-
This account has used all the included free storage (<%= number_to_human_size(Account::StorageLimited::STORAGE_LIMIT) %>).
+
This account has used all the included free storage (<%= number_to_human_size(Current.account.storage_limit) %>).
<%= link_to "Self-host Fizzy", "https://github.com/basecamp/fizzy", target: "_blank" %> for unlimited storage.
diff --git a/saas/app/views/cards/container/footer/saas/_storage_limit_notice.html.erb b/saas/app/views/cards/container/footer/saas/_storage_limit_notice.html.erb
index a45d2ef74..f6f84e17a 100644
--- a/saas/app/views/cards/container/footer/saas/_storage_limit_notice.html.erb
+++ b/saas/app/views/cards/container/footer/saas/_storage_limit_notice.html.erb
@@ -1,6 +1,6 @@
<% if Current.account.nearing_storage_limit? && !Current.identity.staff? %>
- This account has used
<%= number_to_human_size(Current.account.bytes_used) %> of
<%= number_to_human_size(Account::StorageLimited::STORAGE_LIMIT) %> storage.
+ This account has used
<%= number_to_human_size(Current.account.bytes_used) %> of
<%= number_to_human_size(Current.account.storage_limit) %> storage.
<%= link_to "Self-host Fizzy", "https://github.com/basecamp/fizzy", class: "txt-current txt-underline", target: "_blank" %> for unlimited storage.
<% end %>
diff --git a/saas/db/migrate/20260319142914_create_account_storage_exceptions.rb b/saas/db/migrate/20260319142914_create_account_storage_exceptions.rb
new file mode 100644
index 000000000..a68db0e0c
--- /dev/null
+++ b/saas/db/migrate/20260319142914_create_account_storage_exceptions.rb
@@ -0,0 +1,10 @@
+class CreateAccountStorageExceptions < ActiveRecord::Migration[8.2]
+ def change
+ create_table :account_storage_exceptions, id: :uuid do |t|
+ t.references :account, null: false, type: :uuid, index: { unique: true }
+ t.bigint :bytes_allowed, null: false
+
+ t.timestamps
+ end
+ end
+end
diff --git a/saas/db/saas_schema.rb b/saas/db/saas_schema.rb
index 5ae786764..1b6264f05 100644
--- a/saas/db/saas_schema.rb
+++ b/saas/db/saas_schema.rb
@@ -10,7 +10,15 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[8.2].define(version: 2026_03_17_000000) do
+ActiveRecord::Schema[8.2].define(version: 2026_03_19_142914) do
+ create_table "account_storage_exceptions", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ t.uuid "account_id", null: false
+ t.bigint "bytes_allowed", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["account_id"], name: "index_account_storage_exceptions_on_account_id", unique: true
+ end
+
create_table "action_push_native_devices", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.datetime "created_at", null: false
t.string "name"
diff --git a/saas/test/models/account/storage_exception_test.rb b/saas/test/models/account/storage_exception_test.rb
new file mode 100644
index 000000000..f904f04a3
--- /dev/null
+++ b/saas/test/models/account/storage_exception_test.rb
@@ -0,0 +1,46 @@
+require "test_helper"
+
+class Account::StorageExceptionTest < ActiveSupport::TestCase
+ test "storage limit returns default when no exception exists" do
+ assert_equal Account::StorageLimited::DEFAULT_STORAGE_LIMIT, accounts(:initech).storage_limit
+ end
+
+ test "storage limit returns exception value when one exists" do
+ account = accounts(:initech)
+ account.add_storage_exception(5.gigabytes)
+
+ assert_equal 5.gigabytes, account.storage_limit
+ end
+
+ test "add storage exception creates a new record" do
+ account = accounts(:initech)
+
+ assert_difference -> { Account::StorageException.count } do
+ account.add_storage_exception(2.gigabytes)
+ end
+
+ assert_equal 2.gigabytes, account.storage_exception.bytes_allowed
+ end
+
+ test "add storage exception updates existing record" do
+ account = accounts(:initech)
+ account.add_storage_exception(2.gigabytes)
+
+ assert_no_difference -> { Account::StorageException.count } do
+ account.add_storage_exception(10.gigabytes)
+ end
+
+ assert_equal 10.gigabytes, account.storage_exception.reload.bytes_allowed
+ end
+
+ test "exceeding storage limit respects exception" do
+ account = accounts(:initech)
+ Account.any_instance.stubs(:bytes_used).returns(2.gigabytes)
+
+ assert account.exceeding_storage_limit?
+
+ account.add_storage_exception(5.gigabytes)
+
+ assert_not account.exceeding_storage_limit?
+ end
+end