package web import ( "net/http" "net/url" ) func urlQuery(s string) string { return url.QueryEscape(s) } func (s *Server) rootRoutes() http.Handler { m := http.NewServeMux() m.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("ok")) }) m.HandleFunc("GET /webadmin", s.handleLoginForm) m.HandleFunc("POST /webadmin", s.handleLogin) m.HandleFunc("POST /logout", s.handleLogout) m.HandleFunc("GET /dashboard", s.requireAuth(s.handleDashboard)) m.HandleFunc("GET /account/password", s.requireAuth(s.handlePasswordForm)) m.HandleFunc("POST /account/password", s.requireAuth(s.handlePassword)) // blog management (owner or superadmin) m.HandleFunc("GET /b/{sub}/{$}", s.withBlog(s.handleBlogOverview)) m.HandleFunc("GET /b/{sub}/settings", s.withBlog(s.handleSettingsForm)) m.HandleFunc("POST /b/{sub}/settings", s.withBlog(s.handleSettings)) m.HandleFunc("GET /b/{sub}/pages", s.withBlog(s.handlePages)) m.HandleFunc("GET /b/{sub}/pages/new", s.withBlog(s.handlePageForm)) m.HandleFunc("POST /b/{sub}/pages/new", s.withBlog(s.handlePageSave)) m.HandleFunc("GET /b/{sub}/pages/{id}/edit", s.withBlog(s.handlePageForm)) m.HandleFunc("POST /b/{sub}/pages/{id}/edit", s.withBlog(s.handlePageSave)) m.HandleFunc("GET /b/{sub}/pages/{id}/delete", s.withBlog(s.handlePageDeleteConfirm)) m.HandleFunc("POST /b/{sub}/pages/{id}/delete", s.withBlog(s.handlePageDelete)) m.HandleFunc("POST /b/{sub}/pages/{id}/move", s.withBlog(s.handlePageMove)) m.HandleFunc("POST /b/{sub}/pages/{id}/home", s.withBlog(s.handlePageHome)) m.HandleFunc("GET /b/{sub}/posts", s.withBlog(s.handlePosts)) m.HandleFunc("GET /b/{sub}/posts/new", s.withBlog(s.handlePostForm)) m.HandleFunc("POST /b/{sub}/posts/new", s.withBlog(s.handlePostSave)) m.HandleFunc("GET /b/{sub}/posts/{id}/edit", s.withBlog(s.handlePostForm)) m.HandleFunc("POST /b/{sub}/posts/{id}/edit", s.withBlog(s.handlePostSave)) m.HandleFunc("GET /b/{sub}/posts/{id}/delete", s.withBlog(s.handlePostDeleteConfirm)) m.HandleFunc("POST /b/{sub}/posts/{id}/delete", s.withBlog(s.handlePostDelete)) m.HandleFunc("POST /b/{sub}/preview", s.withBlog(s.handlePreview)) m.HandleFunc("GET /b/{sub}/announcements", s.withBlog(s.handleSections)) m.HandleFunc("GET /b/{sub}/announcements/new", s.withBlog(s.handleSectionForm)) m.HandleFunc("POST /b/{sub}/announcements/new", s.withBlog(s.handleSectionSave)) m.HandleFunc("GET /b/{sub}/announcements/{id}/edit", s.withBlog(s.handleSectionForm)) m.HandleFunc("POST /b/{sub}/announcements/{id}/edit", s.withBlog(s.handleSectionSave)) m.HandleFunc("GET /b/{sub}/announcements/{id}/delete", s.withBlog(s.handleSectionDeleteConfirm)) m.HandleFunc("POST /b/{sub}/announcements/{id}/delete", s.withBlog(s.handleSectionDelete)) m.HandleFunc("POST /b/{sub}/announcements/{id}/move", s.withBlog(s.handleSectionMove)) m.HandleFunc("POST /b/{sub}/announcements/{id}/toggle", s.withBlog(s.handleSectionToggle)) m.HandleFunc("GET /b/{sub}/layout", s.withBlog(s.handleLayout)) m.HandleFunc("POST /b/{sub}/layout/widths", s.withBlog(s.handleLayoutWidths)) m.HandleFunc("POST /b/{sub}/layout/areas/{area}/toggle", s.withBlog(s.handleAreaToggle)) m.HandleFunc("POST /b/{sub}/layout/areas/{area}/add", s.withBlog(s.handleModuleAdd)) m.HandleFunc("GET /b/{sub}/layout/modules/{id}/edit", s.withBlog(s.handleModuleForm)) m.HandleFunc("POST /b/{sub}/layout/modules/{id}/edit", s.withBlog(s.handleModuleSave)) m.HandleFunc("POST /b/{sub}/layout/modules/{id}/move", s.withBlog(s.handleModuleMove)) m.HandleFunc("GET /b/{sub}/layout/modules/{id}/delete", s.withBlog(s.handleModuleDeleteConfirm)) m.HandleFunc("POST /b/{sub}/layout/modules/{id}/delete", s.withBlog(s.handleModuleDelete)) m.HandleFunc("POST /b/{sub}/layout/menu/add-page", s.withBlog(s.handleMenuAddPage)) m.HandleFunc("POST /b/{sub}/layout/menu/add-link", s.withBlog(s.handleMenuAddLink)) m.HandleFunc("POST /b/{sub}/layout/menu/{id}/move", s.withBlog(s.handleMenuMove)) m.HandleFunc("POST /b/{sub}/layout/menu/{id}/delete", s.withBlog(s.handleMenuDelete)) m.HandleFunc("GET /b/{sub}/design", s.withBlog(s.handleDesignForm)) m.HandleFunc("POST /b/{sub}/design", s.withBlog(s.handleDesign)) m.HandleFunc("POST /b/{sub}/design/preset", s.withBlog(s.handleDesignPreset)) m.HandleFunc("POST /b/{sub}/design/reset", s.withBlog(s.handleDesignReset)) m.HandleFunc("GET /b/{sub}/files", s.withBlog(s.handleFiles)) m.HandleFunc("POST /b/{sub}/files/upload", s.withBlogFiles(maxUploadFiles, s.handleFileUpload)) m.HandleFunc("POST /b/{sub}/files/{id}/rename", s.withBlog(s.handleFileRename)) m.HandleFunc("POST /b/{sub}/files/{id}/delete", s.withBlog(s.handleFileDelete)) // Files live in the blog's database, so dashboard previews on this host go through /b/. m.HandleFunc("GET /b/{sub}/media/{id}", s.withBlog(s.handleMedia)) // superadmin m.HandleFunc("GET /admin/{$}", s.requireAdmin(s.handleAdmin)) m.HandleFunc("GET /admin/users/new", s.requireAdmin(s.handleAdminNewUserForm)) m.HandleFunc("POST /admin/users/new", s.requireAdmin(s.handleAdminNewUser)) m.HandleFunc("POST /admin/users/{id}/reset-password", s.requireAdmin(s.handleAdminResetPassword)) m.HandleFunc("POST /admin/users/{id}/disable", s.requireAdmin(s.handleAdminSetDisabled(true))) m.HandleFunc("POST /admin/users/{id}/enable", s.requireAdmin(s.handleAdminSetDisabled(false))) m.HandleFunc("GET /admin/users/{id}/delete", s.requireAdmin(s.handleAdminDeleteUserConfirm)) m.HandleFunc("POST /admin/users/{id}/delete", s.requireAdmin(s.handleAdminDeleteUser)) m.HandleFunc("POST /admin/blogs/{id}/upload-limit", s.requireAdmin(s.handleAdminUploadLimit)) m.HandleFunc("GET /media/{id}", s.hostBlog(s.handleMedia)) m.Handle("GET /static/{file}", s.staticHandler()) m.HandleFunc("GET /favicon.ico", s.hostBlog(s.handleFavicon)) // The root domain also serves the superadmin's blog. Literal routes above win // over these wildcards; see reservedPageSlugs for the names that are shadowed. s.blogRoutes(m, s.hostBlog) return s.session(m) } // blogRoutes registers the public blog pages; wrap resolves the blog into the request context. func (s *Server) blogRoutes(m *http.ServeMux, wrap func(http.HandlerFunc) http.HandlerFunc) { m.HandleFunc("GET /{$}", wrap(s.handleBlogHome)) m.HandleFunc("GET /feed.xml", wrap(s.handleBlogFeed)) m.HandleFunc("GET /{page}", wrap(s.handleBlogPage)) m.HandleFunc("GET /{page}/{post}", wrap(s.handleBlogPost)) } func (s *Server) subdomainRoutes() http.Handler { m := http.NewServeMux() // Bloggers type /webadmin on their own blog; send them to the real login page. m.HandleFunc("GET /webadmin", s.handleWebadminRedirect) m.HandleFunc("GET /media/{id}", s.hostBlog(s.handleMedia)) m.Handle("GET /static/{file}", s.staticHandler()) m.HandleFunc("GET /favicon.ico", s.hostBlog(s.handleFavicon)) s.blogRoutes(m, s.hostBlog) return m } func (s *Server) staticHandler() http.Handler { fs := http.StripPrefix("/static/", http.FileServer(http.FS(s.tpl.AssetsFS()))) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !s.cfg.Dev { w.Header().Set("Cache-Control", "public, max-age=3600") } fs.ServeHTTP(w, r) }) }