package web import ( "net/http" "net/url" "sort" "strings" "git.sr.ht/~sircmpwn/core-go/auth" "git.sr.ht/~sircmpwn/core-go/config" "github.com/vaughan0/go-ini" "go.bigb.es/sourcehut-dolt/authn" ) // serviceName is our own service key in the shared config and nav. const serviceName = "dolt.sr.ht" // networkOrder is upstream core.sr.ht's fixed nav ordering (flask.py // _network_order). Services present in the config but not listed here sort // after these, alphabetically. paste.sr.ht and pages.sr.ht are excluded from // the network entirely (they have no user-facing nav), mirroring _network. var networkOrder = []string{ "hub.sr.ht", "git.sr.ht", "hg.sr.ht", "lists.sr.ht", "todo.sr.ht", "builds.sr.ht", "man.sr.ht", "meta.sr.ht", } var networkExcluded = map[string]bool{ "paste.sr.ht": true, "pages.sr.ht": true, } // navEntry is one service link in the shared nav. type navEntry struct { // Name is the leading segment of the service (e.g. "git" for git.sr.ht), // rendered as the link text exactly as upstream nav.html does. Name string // Site is the full service key (e.g. "git.sr.ht"). Site string // Origin is the external URL for the service. Origin string // Active is true for our own service, which highlights it in the nav. Active bool } // basePage is the chrome model shared by every rendered page. Handler view // structs embed it so templates reference its fields directly (e.g. .SiteName). type basePage struct { Title string Site string SiteLabel string SiteName string Environment string ShowEnvBanner bool Network []navEntry MetaOrigin string SelfOrigin string LoginURL string LogoutURL string StyleHref string // CurrentUser is the authenticated caller, or nil for an anonymous request. CurrentUser *auth.AuthContext } // buildNetwork returns the ordered nav entries for conf: every section ending // ".sr.ht" that is not excluded, ordered by networkOrder then alphabetically // for unknown services. Origins are resolved externally via config.GetOrigin. func buildNetwork(conf ini.File) []navEntry { var sites []string for section := range conf { if !strings.HasSuffix(section, ".sr.ht") || networkExcluded[section] { continue } sites = append(sites, section) } orderIndex := func(s string) int { for i, n := range networkOrder { if n == s { return i } } return len(networkOrder) // unknown services sort after the fixed set } sort.Slice(sites, func(i, j int) bool { oi, oj := orderIndex(sites[i]), orderIndex(sites[j]) if oi != oj { return oi < oj } return sites[i] < sites[j] // stable, alphabetical among unknowns }) entries := make([]navEntry, 0, len(sites)) for _, s := range sites { entries = append(entries, navEntry{ Name: strings.SplitN(s, ".", 2)[0], Site: s, Origin: config.GetOrigin(conf, s, true), Active: s == serviceName, }) } return entries } // newBasePage builds the chrome model for a request. Title is the per-page // ; the caller sets any page-specific fields on its own view struct. func (a *app) newBasePage(r *http.Request, title string) basePage { conf := a.cfg.Conf env := config.GetString(conf, "sr.ht", "environment", "development") self := config.GetOrigin(conf, serviceName, true) meta := config.GetOrigin(conf, "meta.sr.ht", true) return basePage{ Title: title, Site: serviceName, SiteLabel: strings.SplitN(serviceName, ".", 2)[0], SiteName: config.GetString(conf, "sr.ht", "site-name", "sr.ht"), Environment: env, ShowEnvBanner: env != "production", Network: buildNetwork(conf), MetaOrigin: meta, SelfOrigin: self, LoginURL: loginURL(self, meta, r), LogoutURL: logoutURL(self, meta), StyleHref: a.styleHref, CurrentUser: authn.CallerFromContext(r.Context()), } } // loginURL mirrors core.sr.ht flask.py: {meta}/login?return_to={self+full_path}. // full_path is the request path with its query string, so login round-trips the // user back to exactly where they were. func loginURL(self, meta string, r *http.Request) string { returnTo := self + r.URL.EscapedPath() if r.URL.RawQuery != "" { returnTo += "?" + r.URL.RawQuery } return meta + "/login?return_to=" + url.QueryEscape(returnTo) } // logoutURL mirrors core.sr.ht flask.py: {meta}/logout?return_to={self origin}. func logoutURL(self, meta string) string { return meta + "/logout?return_to=" + url.QueryEscape(self) }