aboutsummaryrefslogtreecommitdiffstats
path: root/internal/web/server.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/web/server.go')
-rw-r--r--internal/web/server.go43
1 files changed, 27 insertions, 16 deletions
diff --git a/internal/web/server.go b/internal/web/server.go
index e8a5e6d..68dba3b 100644
--- a/internal/web/server.go
+++ b/internal/web/server.go
@@ -29,33 +29,26 @@ type Server struct {
func NewServer(cfg *config.Config, st *store.Store) *Server {
s := &Server{cfg: cfg, st: st, tpl: newTemplates(cfg.Dev, funcs)}
s.root = s.rootRoutes()
- s.blog = s.blogRoutes()
+ s.blog = s.subdomainRoutes()
return s
}
// ServeHTTP dispatches on the Host header: the base domain is the management
-// site, one label below it is a blog, anything else is a 404.
+// site plus the superadmin's root blog, one label below it is a blog, anything
+// else is a 404.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
host := hostname(r.Host)
switch {
- case host == s.cfg.BaseDomain, host == "www."+s.cfg.BaseDomain:
- s.root.ServeHTTP(w, r)
+ case host == s.cfg.BaseDomain, host == config.RootSubdomain+"."+s.cfg.BaseDomain:
+ // The root blog is looked up lazily by hostBlog so management pages work even without one.
+ s.root.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), ctxHostSub, config.RootSubdomain)))
case strings.HasSuffix(host, "."+s.cfg.BaseDomain):
sub := strings.TrimSuffix(host, "."+s.cfg.BaseDomain)
if strings.Contains(sub, ".") || reservedSubdomains[sub] {
http.NotFound(w, r)
return
}
- blog, err := s.st.BlogBySubdomain(r.Context(), sub)
- if err != nil {
- if errors.Is(err, store.ErrNotFound) {
- s.plainError(w, http.StatusNotFound, "No blog here (yet).")
- return
- }
- s.serverError(w, err)
- return
- }
- s.blog.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), ctxBlog, blog)))
+ s.blog.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), ctxHostSub, sub)))
default:
s.plainError(w, http.StatusNotFound, fmt.Sprintf("Unknown host %q. Blogs live at <name>.%s", host, s.cfg.BaseDomain))
}
@@ -73,8 +66,9 @@ func hostname(h string) string {
type ctxKey int
const (
- ctxUser ctxKey = iota
- ctxBlog
+ ctxUser ctxKey = iota
+ ctxBlog // the blog a handler operates on (public page, or /b/{sub}/ management)
+ ctxHostSub // subdomain derived from the Host header
)
func currentUser(r *http.Request) *store.User {
@@ -89,6 +83,23 @@ func currentBlog(r *http.Request) *store.Blog {
// ---- middleware ----------------------------------------------------------
+// hostBlog resolves the blog named by the Host header into the context for public pages.
+func (s *Server) hostBlog(next http.HandlerFunc) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ sub, _ := r.Context().Value(ctxHostSub).(string)
+ blog, err := s.st.BlogBySubdomain(r.Context(), sub)
+ if err != nil {
+ if errors.Is(err, store.ErrNotFound) {
+ s.plainError(w, http.StatusNotFound, "No blog here (yet).")
+ return
+ }
+ s.serverError(w, err)
+ return
+ }
+ next(w, r.WithContext(context.WithValue(r.Context(), ctxBlog, blog)))
+ }
+}
+
// session loads the user from the JWT cookie (if any) into the context.
func (s *Server) session(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {