berrypod/lib/berrypod_web/router.ex
jamey 045be2ed7e
All checks were successful
deploy / deploy (push) Successful in 1m21s
add admin CRUD for custom CMS pages
New settings form for creating and editing custom page metadata
(title, slug, meta description, published, nav settings). Pages
index shows custom pages section with draft badges and delete.
Editor shows settings button for custom pages, hides reset to
defaults. 20 new tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 09:43:03 +00:00

282 lines
9.0 KiB
Elixir

defmodule BerrypodWeb.Router do
use BerrypodWeb, :router
import BerrypodWeb.UserAuth
import Phoenix.LiveDashboard.Router
import ErrorTracker.Web.Router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, html: {BerrypodWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
plug :fetch_current_scope_for_user
plug BerrypodWeb.Plugs.CountryDetect
plug BerrypodWeb.Plugs.LoadTheme
end
pipeline :api do
plug :accepts, ["json"]
end
# Lightweight pipeline for SVG recoloring — no session, CSRF, auth, or layout
pipeline :image do
plug :put_secure_browser_headers
end
# Minimal pipeline for robots.txt and sitemap.xml
pipeline :seo do
plug :put_secure_browser_headers
end
pipeline :printify_webhook do
plug BerrypodWeb.Plugs.VerifyPrintifyWebhook
end
pipeline :printful_webhook do
plug BerrypodWeb.Plugs.VerifyPrintfulWebhook
end
pipeline :shop do
plug :put_root_layout, html: {BerrypodWeb.Layouts, :shop_root}
plug BerrypodWeb.Plugs.LoadTheme
plug BerrypodWeb.Plugs.Analytics
end
pipeline :admin do
plug :put_root_layout, html: {BerrypodWeb.Layouts, :admin_root}
plug BerrypodWeb.Plugs.LoadTheme
end
# ── Routes without the :browser pipeline ──────────────────────────
# Health check (no auth, no theme loading — for load balancers and uptime monitors)
scope "/", BerrypodWeb do
pipe_through [:api]
get "/health", HealthController, :show
end
# SEO — crawlers need these without any session/auth overhead
scope "/", BerrypodWeb do
pipe_through [:seo]
get "/robots.txt", SeoController, :robots
get "/sitemap.xml", SeoController, :sitemap
end
# Favicon & PWA manifest — served from DB, minimal pipeline
scope "/", BerrypodWeb do
pipe_through [:seo]
get "/favicon.svg", FaviconController, :favicon_svg
get "/favicon-32x32.png", FaviconController, :favicon_32
get "/apple-touch-icon.png", FaviconController, :apple_touch_icon
get "/icon-192.png", FaviconController, :icon_192
get "/icon-512.png", FaviconController, :icon_512
get "/site.webmanifest", FaviconController, :webmanifest
end
# SVG recoloring (dynamic — can't be pre-generated to disk)
scope "/images", BerrypodWeb do
pipe_through :image
get "/:id/recolored/:color", ImageController, :recolored_svg
end
# Webhook endpoints (no CSRF, signature verified)
scope "/webhooks", BerrypodWeb do
pipe_through [:api, :printify_webhook]
post "/printify", WebhookController, :printify
end
scope "/webhooks", BerrypodWeb do
pipe_through [:api, :printful_webhook]
post "/printful", WebhookController, :printful
end
scope "/webhooks", BerrypodWeb do
pipe_through [:api]
post "/stripe", StripeWebhookController, :handle
end
# ── Routes with the :browser pipeline ─────────────────────────────
# All routes below use :browser. The shop scope with its /:slug
# catch-all MUST be last so it doesn't intercept other routes.
# Setup page — minimal live_session, no theme/cart/search hooks
scope "/", BerrypodWeb do
pipe_through [:browser]
get "/setup/login/:token", SetupController, :login
get "/recover/login/:token", SetupController, :recover_login
live_session :setup,
on_mount: [{BerrypodWeb.UserAuth, :mount_current_scope}] do
live "/setup", Setup.Onboarding, :index
live "/recover", Setup.Recover, :index
end
end
# LiveDashboard and ErrorTracker behind admin auth (available in all environments)
scope "/admin" do
pipe_through [:browser, :require_authenticated_user]
live_dashboard "/dashboard", metrics: BerrypodWeb.Telemetry
error_tracker_dashboard("/errors")
end
# Admin pages with sidebar layout
scope "/admin", BerrypodWeb do
pipe_through [:browser, :require_authenticated_user, :admin]
get "/analytics/export", AnalyticsExportController, :export
live_session :admin,
layout: {BerrypodWeb.Layouts, :admin},
on_mount: [
{BerrypodWeb.UserAuth, :require_authenticated},
{BerrypodWeb.AdminLayoutHook, :assign_current_path}
] do
live "/", Admin.Dashboard, :index
live "/analytics", Admin.Analytics, :index
live "/orders", Admin.Orders, :index
live "/orders/:id", Admin.OrderShow, :show
live "/products", Admin.Products, :index
live "/products/:id", Admin.ProductShow, :show
live "/providers", Admin.Providers.Index, :index
live "/providers/new", Admin.Providers.Form, :new
live "/providers/:id/edit", Admin.Providers.Form, :edit
live "/settings", Admin.Settings, :index
live "/settings/email", Admin.EmailSettings, :index
live "/pages", Admin.Pages.Index, :index
live "/pages/new", Admin.Pages.CustomForm, :new
live "/pages/:slug/settings", Admin.Pages.CustomForm, :edit
live "/pages/:slug", Admin.Pages.Editor, :edit
live "/media", Admin.Media, :index
live "/redirects", Admin.Redirects, :index
end
# Theme editor: admin root layout but full-screen (no sidebar)
live_session :admin_theme,
on_mount: [{BerrypodWeb.UserAuth, :require_authenticated}] do
live "/theme", Admin.Theme.Index, :index
end
end
# User account settings
scope "/", BerrypodWeb do
pipe_through [:browser, :require_authenticated_user]
live_session :user_settings,
on_mount: [{BerrypodWeb.UserAuth, :require_authenticated}] do
live "/users/settings", Auth.Settings, :edit
live "/users/settings/confirm-email/:token", Auth.Settings, :confirm_email
end
post "/users/update-password", UserSessionController, :update_password
end
scope "/", BerrypodWeb do
pipe_through [:browser]
live_session :current_user,
on_mount: [{BerrypodWeb.UserAuth, :mount_current_scope}] do
live "/users/register", Auth.Registration, :new
live "/users/log-in", Auth.Login, :new
live "/users/log-in/:token", Auth.Confirmation, :new
end
post "/users/log-in", UserSessionController, :create
delete "/users/log-out", UserSessionController, :delete
end
# Order lookup verification — sets session email then redirects to /orders
scope "/", BerrypodWeb do
pipe_through [:browser]
get "/orders/verify/:token", OrderLookupController, :verify
get "/unsubscribe/:token", UnsubscribeController, :unsubscribe
end
# Dev-only routes (mailbox preview, error previews)
if Application.compile_env(:berrypod, :dev_routes) do
scope "/dev" do
pipe_through :browser
forward "/mailbox", Plug.Swoosh.MailboxPreview
# Preview error pages
get "/errors/404", BerrypodWeb.ErrorPreviewController, :not_found
get "/errors/500", BerrypodWeb.ErrorPreviewController, :server_error
end
end
# Cart API (session persistence for LiveView)
scope "/api", BerrypodWeb do
pipe_through [:browser]
post "/cart", CartController, :update
end
# Public storefront — MUST be last because /:slug catch-all absorbs
# any single-segment path not matched above
scope "/", BerrypodWeb do
pipe_through [:browser, :shop]
live_session :coming_soon,
layout: {BerrypodWeb.Layouts, :shop},
on_mount: [
{BerrypodWeb.ThemeHook, :mount_theme}
] do
live "/coming-soon", Shop.ComingSoon, :index
end
live_session :public_shop,
layout: {BerrypodWeb.Layouts, :shop},
on_mount: [
{BerrypodWeb.UserAuth, :mount_current_scope},
{BerrypodWeb.ThemeHook, :mount_theme},
{BerrypodWeb.ThemeHook, :require_site_live},
{BerrypodWeb.CartHook, :mount_cart},
{BerrypodWeb.SearchHook, :mount_search},
{BerrypodWeb.AnalyticsHook, :track},
{BerrypodWeb.PageEditorHook, :mount_page_editor}
] do
live "/", Shop.Home, :index
live "/about", Shop.Content, :about
live "/delivery", Shop.Content, :delivery
live "/privacy", Shop.Content, :privacy
live "/terms", Shop.Content, :terms
live "/contact", Shop.Contact, :index
live "/collections/:slug", Shop.Collection, :show
live "/products/:id", Shop.ProductShow, :show
live "/cart", Shop.Cart, :index
live "/search", Shop.Search, :index
live "/checkout/success", Shop.CheckoutSuccess, :show
live "/orders", Shop.Orders, :index
live "/orders/:order_number", Shop.OrderDetail, :show
# Catch-all for custom CMS pages — must be last
live "/:slug", Shop.CustomPage, :show
end
# Checkout (POST — creates Stripe session and redirects)
post "/checkout", CheckoutController, :create
# Order lookup (no-JS fallback for contact page form)
post "/contact/lookup", OrderLookupController, :lookup
# Cart form actions (no-JS fallbacks for LiveView cart events)
post "/cart/add", CartController, :add
post "/cart/remove", CartController, :remove
post "/cart/update", CartController, :update_item
post "/cart/country", CartController, :update_country
end
end