# Edge for the single-domain production stack: TLS termination (automatic ACME
# certificates), HSTS + baseline headers, and path/conneg routing that puts the
# client, the API, record IRIs, and Keycloak on ONE origin.
#
# For a public DNS name nothing needs configuring — Caddy obtains and renews
# the certificate itself. To smoke-test on a host without public DNS, add
# `tls internal` inside the site block (self-signed via Caddy's local CA).

{
	email {$ACME_EMAIL}
}

{$PUBLIC_HOST} {
	# Compress ONLY the SPA's hashed static assets. Caddy's encode handler
	# rewrites ETags on compressed responses ("abc" → "abc-zstd"), and the
	# FDP's optimistic-concurrency contract round-trips ETags via If-Match —
	# a rewritten ETag makes every edit fail with "If-Match does not match
	# the current resource ETag". Assets are content-hashed and never
	# edited, so they are the one place compression is safe (and the only
	# place it pays: the JS bundles).
	@compressible path /assets/*
	encode @compressible zstd gzip

	header {
		Strict-Transport-Security "max-age=63072000; includeSubDomains"
		X-Content-Type-Options "nosniff"
		Referrer-Policy "no-referrer"
		# Strip upstream software/version disclosure. (No X-Frame-Options at
		# the edge: Keycloak and the client manage their own frame policy —
		# the OIDC session iframe must keep working.)
		-Server
		-X-Powered-By
	}

	request_body {
		max_size 20MB
	}

	# --- the API: everything the server namespaces under /fdp-api ------------
	handle /fdp-api/* {
		reverse_proxy server:8000
	}

	# --- SPA OIDC callback ------------------------------------------------------
	# The client's redirect_uri is <origin>/auth/callback — an SPA route, NOT a
	# Keycloak path. It must be matched before the /auth/* Keycloak route or
	# login never completes. (Found by the semlab-leiden deployment.)
	handle /auth/callback {
		reverse_proxy client:80
	}

	# --- Keycloak under /auth (KC_HTTP_RELATIVE_PATH) -------------------------
	handle /auth/* {
		reverse_proxy keycloak:8080
	}

	# --- MCP bridge (only with `--profile mcp`; 502 on this path otherwise) --
	handle /mcp* {
		reverse_proxy mcp:8000
	}

	# --- SPA static files ------------------------------------------------------
	handle /assets/* {
		reverse_proxy client:80
	}
	handle /config.js {
		reverse_proxy client:80
	}
	handle /favicon* {
		reverse_proxy client:80
	}
	handle /robots.txt {
		reverse_proxy client:80
	}

	# --- the shared IRI space --------------------------------------------------
	# Every remaining path is a resource IRI (records live at the origin root,
	# any prefix except fdp-api) AND a potential SPA route. Split on content
	# negotiation, exactly like the FDP reference implementation: browsers
	# (Accept: text/html) get the SPA, which fetches the record over /fdp-api;
	# RDF clients dereference the IRI straight to the server.
	@html header Accept *text/html*
	handle {
		handle @html {
			reverse_proxy client:80
		}
		handle {
			reverse_proxy server:8000
		}
	}
}
