Add per-account storage exceptions for SaaS (#2733)
Allow granting specific accounts more storage via an account_storage_exceptions table in the saas DB, without relying on the blanket staff bypass.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
class Account::StorageException < SaasRecord
|
||||
belongs_to :account
|
||||
|
||||
validates :bytes_allowed, presence: true, numericality: { greater_than: 0 }
|
||||
end
|
||||
@@ -1,14 +1,30 @@
|
||||
module Account::StorageLimited
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
STORAGE_LIMIT = 1.gigabyte
|
||||
DEFAULT_STORAGE_LIMIT = 1.gigabyte
|
||||
NEAR_STORAGE_LIMIT_THRESHOLD = 500.megabytes
|
||||
|
||||
included do
|
||||
has_one :storage_exception
|
||||
end
|
||||
|
||||
def storage_limit
|
||||
storage_exception&.bytes_allowed || DEFAULT_STORAGE_LIMIT
|
||||
end
|
||||
|
||||
def exceeding_storage_limit?
|
||||
bytes_used > STORAGE_LIMIT
|
||||
bytes_used > storage_limit
|
||||
end
|
||||
|
||||
def nearing_storage_limit?
|
||||
!exceeding_storage_limit? && bytes_used > STORAGE_LIMIT - NEAR_STORAGE_LIMIT_THRESHOLD
|
||||
!exceeding_storage_limit? && bytes_used > storage_limit - NEAR_STORAGE_LIMIT_THRESHOLD
|
||||
end
|
||||
|
||||
def add_storage_exception(bytes)
|
||||
if storage_exception
|
||||
storage_exception.update!(bytes_allowed: bytes)
|
||||
else
|
||||
create_storage_exception!(bytes_allowed: bytes)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="comment__content flex flex-column flex-item-grow full-width">
|
||||
<div class="comment__body lexxy-content">
|
||||
<div class="action-text-content">
|
||||
<strong>This account has used all the included free storage (<%= number_to_human_size(Account::StorageLimited::STORAGE_LIMIT) %>).</strong>
|
||||
<strong>This account has used all the included free storage (<%= number_to_human_size(Current.account.storage_limit) %>).</strong>
|
||||
<div><%= link_to "Self-host Fizzy", "https://github.com/basecamp/fizzy", target: "_blank" %> for unlimited storage.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="card-perma__notch card-perma__notch--bottom">
|
||||
<div class="card-perma__account-limit-message">
|
||||
<strong>This account has used all the included free storage (<%= number_to_human_size(Account::StorageLimited::STORAGE_LIMIT) %>).</strong>
|
||||
<strong>This account has used all the included free storage (<%= number_to_human_size(Current.account.storage_limit) %>).</strong>
|
||||
<div>
|
||||
<%= link_to "Self-host Fizzy", "https://github.com/basecamp/fizzy", target: "_blank" %> for unlimited storage.
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<% if Current.account.nearing_storage_limit? && !Current.identity.staff? %>
|
||||
<div class="txt-small">
|
||||
This account has used <strong><%= number_to_human_size(Current.account.bytes_used) %></strong> of <strong><%= number_to_human_size(Account::StorageLimited::STORAGE_LIMIT) %></strong> storage.
|
||||
This account has used <strong><%= number_to_human_size(Current.account.bytes_used) %></strong> of <strong><%= number_to_human_size(Current.account.storage_limit) %></strong> storage.
|
||||
<div><%= link_to "Self-host Fizzy", "https://github.com/basecamp/fizzy", class: "txt-current txt-underline", target: "_blank" %> for unlimited storage.</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user