Configure static upload serving inside Web
The :static plugin needs the storage root at load, which forced its
config into config.ru (and a duplicate in the test helper). A shared
Domus.config lets Web read the path in its own class body.
Assisted-by: Claude Opus 4.8 via Claude Code
diff --git a/config.ru b/config.ru
index eef2d7e..1031a7d 100644
--- a/config.ru
+++ b/config.ru
@@ -10,10 +10,4 @@ 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/domus/app.rb b/lib/domus/app.rb
index 7419a30..bcc3ee7 100644
--- a/lib/domus/app.rb
+++ b/lib/domus/app.rb
@@ -8,7 +8,7 @@ module Domus
attr_reader :config, :db
# : (Config) -> void
- def initialize(config = Config.env)
+ def initialize(config = Domus.config)
@config = config
@db = Sequel.sqlite(config.database_url)
@db.extension(:sole)
diff --git a/lib/domus/config.rb b/lib/domus/config.rb
index 88169c1..3c68f29 100644
--- a/lib/domus/config.rb
+++ b/lib/domus/config.rb
@@ -15,4 +15,15 @@ module Domus
storage_path: Pathname(ENV.fetch("STORAGE_PATH") { "storage" }),
)
end
+
+ class << self
+ # The process-wide config, resolved from the environment the first time
+ # it's read. Loaded code reads this single instance: Web's :static plugin
+ # needs the storage path at load, and App stores files there. Tests assign
+ # a Config here before requiring the app to point it at a temp dir.
+ attr_writer :config
+
+ #: () -> Config
+ def config = @config ||= Config.env
+ end
end
diff --git a/lib/domus/web.rb b/lib/domus/web.rb
index 4e9b455..2cc81b5 100644
--- a/lib/domus/web.rb
+++ b/lib/domus/web.rb
@@ -2,6 +2,7 @@
require "roda"
require "fileutils"
+require_relative "config"
require_relative "views/layout"
require_relative "views/home"
require_relative "views/asset"
@@ -21,6 +22,15 @@ module Domus
class Web < Roda
plugin :public
+
+ # Serve stored uploads (storage/files/{id}{ext}) at /files/ straight off
+ # disk. The :static plugin needs a concrete root at load time, so it reads
+ # the shared Domus.config (the same instance App uses). Uploads are
+ # immutable once stored, so they cache indefinitely.
+ plugin :static, ["/files/"],
+ root: Domus.config.storage_path.to_s,
+ cache_control: "private, max-age=31536000, immutable"
+
plugin :all_verbs
plugin :error_handler do |e|
case e
@@ -55,8 +65,8 @@ module Domus
# 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.
+ # by the :static middleware configured above. POST /files stays on the
+ # route.
r.on "files" do
r.post do
save_file(r.params)
diff --git a/sig/generated/domus/config.rbs b/sig/generated/domus/config.rbs
index 381cf90..7a54d3c 100644
--- a/sig/generated/domus/config.rbs
+++ b/sig/generated/domus/config.rbs
@@ -18,4 +18,13 @@ module Domus
# : () -> Config
def self.env: () -> Config
end
+
+ # The process-wide config, resolved from the environment the first time
+ # it's read. Loaded code reads this single instance: Web's :static plugin
+ # needs the storage path at load, and App stores files there. Tests assign
+ # a Config here before requiring the app to point it at a temp dir.
+ attr_writer config: untyped
+
+ # : () -> Config
+ def self.config: () -> Config
end
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 6323e8c..2437373 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -5,16 +5,18 @@ require "fileutils"
require "pathname"
require "domus/app"
-require "domus/web"
-require "domus/seeds"
storage = Dir.mktmpdir("domus-test")
at_exit { FileUtils.rm_rf(storage) }
-app = Domus::App.new(Domus::Config.new(database_url: ":memory:", storage_path: Pathname(storage)))
+# Web reads Domus.config at load to configure static upload serving, so inject
+# a temp-dir config before requiring it.
+Domus.config = Domus::Config.new(database_url: ":memory:", storage_path: Pathname(storage))
+
+require "domus/web"
+require "domus/seeds"
+
+app = Domus::App.new
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"