169 lines
4.2 KiB
Elixir
169 lines
4.2 KiB
Elixir
defmodule MoundHuntersWeb.Router do
|
|
@moduledoc """
|
|
Main HTTP router for Hopewell Road application.
|
|
"""
|
|
use Plug.Router
|
|
require Logger
|
|
|
|
# Request logging (must be first to capture timing)
|
|
plug(MoundHuntersWeb.Plugs.RequestLogger)
|
|
|
|
# Parse query params and request body
|
|
plug(Plug.Parsers,
|
|
parsers: [:urlencoded, :json],
|
|
json_decoder: Jason
|
|
)
|
|
|
|
# Bounds checking for Ohio coordinates
|
|
plug(MoundHuntersWeb.Plugs.BoundsCheck)
|
|
|
|
# Match routes
|
|
plug(:match)
|
|
|
|
# Dispatch to matched route
|
|
plug(:dispatch)
|
|
|
|
# Tile endpoints
|
|
get "/tiles/request" do
|
|
MoundHuntersWeb.TileController.request(conn)
|
|
end
|
|
|
|
match "/tiles/:format/:tile_id", via: [:get, :head] do
|
|
conn = put_private(conn, :path_params, %{"tile_id" => tile_id, "format" => format})
|
|
MoundHuntersWeb.TileController.serve(conn)
|
|
end
|
|
|
|
# Geometry sharing API
|
|
post "/api/share" do
|
|
MoundHuntersWeb.ApiController.create_share(conn)
|
|
end
|
|
|
|
get "/api/share/:id" do
|
|
conn = put_private(conn, :path_params, %{"id" => id})
|
|
MoundHuntersWeb.ApiController.get_share(conn)
|
|
end
|
|
|
|
# Tile metadata API
|
|
get "/api/meta/tile/:id" do
|
|
conn = put_private(conn, :path_params, %{"id" => id})
|
|
MoundHuntersWeb.ApiController.get_tile(conn)
|
|
end
|
|
|
|
get "/api/meta/tile" do
|
|
MoundHuntersWeb.ApiController.get_tile_by_coords(conn)
|
|
end
|
|
|
|
# Health check
|
|
get "/health" do
|
|
send_resp(conn, 200, Jason.encode!(%{status: "ok"}))
|
|
end
|
|
|
|
get "/" do
|
|
static_dir = Application.app_dir(:mound_hunters, "priv/static")
|
|
index_path = Path.join(static_dir, "index.html")
|
|
|
|
if File.exists?(index_path) do
|
|
conn
|
|
|> put_resp_header("content-type", "text/html; charset=utf-8")
|
|
|> put_resp_header("cache-control", "no-cache")
|
|
|> send_file(200, index_path)
|
|
else
|
|
send_resp(conn, 404, "Not found")
|
|
end
|
|
end
|
|
|
|
# Static files (with cache busting support via manifest)
|
|
get "/*path" do
|
|
serve_static(conn, path)
|
|
end
|
|
|
|
# 404 handler
|
|
match _ do
|
|
send_resp(conn, 404, Jason.encode!(%{error: "Not found"}))
|
|
end
|
|
|
|
# Serve static files from priv/static
|
|
defp serve_static(conn, path) do
|
|
static_dir = Application.app_dir(:mound_hunters, "priv/static")
|
|
file_path = resolve_static_path(path, static_dir)
|
|
|
|
cond do
|
|
file_path && File.regular?(file_path) ->
|
|
# Determine content type
|
|
content_type = MIME.from_path(file_path)
|
|
|
|
# Set cache headers (immutable for hashed files)
|
|
cache_control =
|
|
if String.contains?(file_path, "-") do
|
|
"public, max-age=31536000, immutable"
|
|
else
|
|
"public, max-age=3600"
|
|
end
|
|
|
|
conn
|
|
|> put_resp_header("content-type", content_type)
|
|
|> put_resp_header("cache-control", cache_control)
|
|
|> send_file(200, file_path)
|
|
|
|
path == [] or path == [""] ->
|
|
# Serve index.html for root
|
|
index_path = Path.join(static_dir, "index.html")
|
|
|
|
if File.exists?(index_path) do
|
|
conn
|
|
|> put_resp_header("content-type", "text/html")
|
|
|> put_resp_header("cache-control", "no-cache")
|
|
|> send_file(200, index_path)
|
|
else
|
|
send_resp(conn, 404, "Not found")
|
|
end
|
|
|
|
true ->
|
|
send_resp(conn, 404, "Not found")
|
|
end
|
|
end
|
|
|
|
# Resolve static file path, checking manifest for hashed versions
|
|
defp resolve_static_path(path, static_dir) do
|
|
requested = Path.join(path)
|
|
direct_path = Path.join(static_dir, requested)
|
|
|
|
cond do
|
|
File.regular?(direct_path) ->
|
|
direct_path
|
|
|
|
# Check manifest for hashed version
|
|
manifest_path = Path.join(static_dir, "manifest.json") ->
|
|
case load_manifest(manifest_path) do
|
|
{:ok, manifest} ->
|
|
hashed = Map.get(manifest, requested)
|
|
|
|
if hashed do
|
|
hashed_path = Path.join(static_dir, hashed)
|
|
if File.regular?(hashed_path), do: hashed_path, else: nil
|
|
else
|
|
nil
|
|
end
|
|
|
|
_ ->
|
|
nil
|
|
end
|
|
|
|
true ->
|
|
nil
|
|
end
|
|
end
|
|
|
|
# Load and cache manifest
|
|
defp load_manifest(path) do
|
|
if File.exists?(path) do
|
|
case File.read(path) do
|
|
{:ok, content} -> Jason.decode(content)
|
|
_ -> {:error, :read_failed}
|
|
end
|
|
else
|
|
{:error, :not_found}
|
|
end
|
|
end
|
|
end
|