All checks were successful
deploy / deploy (push) Successful in 1m21s
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>
282 lines
9.0 KiB
Elixir
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
|