package web
import (
"net/http"
"net/url"
"sort"
"strings"
"sourcecraft.dev/bigbes/sr-ht-core/auth"
"sourcecraft.dev/bigbes/sr-ht-core/config"
"github.com/vaughan0/go-ini"
"sourcecraft.dev/bigbes/sr-ht-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
// <title>; 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)
}