Serve uploads with the Roda :static plugin
Replace the in-route Rack::Files mount with Roda's :static plugin,
wired at boot (config.ru and the test setup) to the app's storage dir
since the path is injected via opts[:app]. The "/files/" prefix serves
stored uploads straight off disk with an immutable cache header, while
the bare POST /files upload route is left untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N92HLTepkg3AtRpDUyZ54o
change
commit 05530cc0f4dd43fd44593d18957c67f8c6403875
author Claude <noreply@anthropic.com>
date
parent 68502f43
diff --git a/config.ru b/config.ru
index 042a8ea..5f0512f 100644
--- a/config.ru
+++ b/config.ru
@@ -11,4 +11,10 @@ app = Domus::App.new
 Sequel::Migrator.run(app.db, "db/migrate") unless Dir.empty?("db/migrate")
 Domus::Web.opts[:app] = app
 
+# Serve stored uploads (storage/files/{id}{ext}) at /files/ straight off disk.
+# Uploads are immutable once stored, so they cache indefinitely.
+Domus::Web.plugin :static, ["/files/"],
+  root: app.config.storage_path.to_s,
+  cache_control: "private, max-age=31536000, immutable"
+
 run Domus::Web
diff --git a/lib/web.rb b/lib/web.rb
index 3516dcf..4e9b455 100644
--- a/lib/web.rb
+++ b/lib/web.rb
@@ -2,7 +2,6 @@
 
 require "roda"
 require "fileutils"
-require "rack/files"
 require_relative "views/layout"
 require_relative "views/home"
 require_relative "views/asset"
@@ -55,17 +54,14 @@ module Domus
       # credential a forged cross-site POST could ride on. If this app ever
       # adopts cookie-based sessions, load the Roda :route_csrf plugin and
       # verify the token here before accepting the upload.
+      # GET /files/:filename (the stored uploads) is served straight off disk
+      # by the :static middleware, wired to the app's storage dir at boot
+      # (see config.ru / the test setup). POST /files stays on the route.
       r.on "files" do
         r.post do
           save_file(r.params)
           r.redirect "/"
         end
-
-        # GET /files/:filename — serve a stored upload straight off disk.
-        # Rack::Files handles content-type, range requests, conditional GETs
-        # and a 404 for unknown files; uploads are named {id}{ext} and are
-        # immutable once stored, so they cache indefinitely.
-        r.run Rack::Files.new(app.files_root, { "cache-control" => "private, max-age=31536000, immutable" })
       end
 
       r.on "assets" do
diff --git a/test/test_app.rb b/test/test_app.rb
index 496c75e..3cea785 100644
--- a/test/test_app.rb
+++ b/test/test_app.rb
@@ -125,6 +125,7 @@ class TestApp < Minitest::Test
     get "/files/#{file[:id]}#{file[:extension]}"
     assert_equal 200, last_response.status
     assert_equal "image/png", last_response.headers["Content-Type"]
+    assert_includes last_response.headers["Cache-Control"].to_s, "immutable"
     assert_equal "fake-png-bytes", last_response.body
   end
 
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 2e316a8..2485587 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -14,3 +14,6 @@ app = Domus::App.new(Domus::Config.new(database_url: ":memory:", storage_path: P
 migrate_dir = File.expand_path("../db/migrate", __dir__)
 Sequel::Migrator.run(app.db, migrate_dir) unless Dir.empty?(migrate_dir)
 Domus::Web.opts[:app] = app
+Domus::Web.plugin :static, ["/files/"],
+  root: app.config.storage_path.to_s,
+  cache_control: "private, max-age=31536000, immutable"