diff --git a/.rubocop.yml b/.rubocop.yml
index 9e252d614..f47dd6fb6 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -335,6 +335,7 @@ Rails/SkipsModelValidations:
ForbiddenMethods:
- update_attribute
Exclude:
+ - app/models/tenant.rb
- lib/acts_as_paranoid_aliases.rb
Rails/TimeZone:
diff --git a/app/components/admin/tenants/index_component.html.erb b/app/components/admin/tenants/index_component.html.erb
index ad5f43ea9..63bb3504e 100644
--- a/app/components/admin/tenants/index_component.html.erb
+++ b/app/components/admin/tenants/index_component.html.erb
@@ -8,20 +8,14 @@
<%= attribute_name(:name) %> |
<%= attribute_name(:schema) %> |
<%= attribute_name(:url) %> |
+ <%= t("admin.tenants.index.enabled") %> |
<%= t("admin.shared.actions") %> |
<% @tenants.each do |tenant| %>
-
- | <%= tenant.name %> |
- <%= tenant.schema %> |
- <%= link_to tenant.host, root_url(host: tenant.host) %> |
-
- <%= render Admin::TableActionsComponent.new(tenant, actions: [:edit]) %>
- |
-
+ <%= render Admin::Tenants::RowComponent.new(tenant) %>
<% end %>
diff --git a/app/components/admin/tenants/row_component.html.erb b/app/components/admin/tenants/row_component.html.erb
new file mode 100644
index 000000000..365866676
--- /dev/null
+++ b/app/components/admin/tenants/row_component.html.erb
@@ -0,0 +1,9 @@
+
+ | <%= tenant.name %> |
+ <%= tenant.schema %> |
+ <%= link_to_unless tenant.hidden?, tenant.host, root_url(host: tenant.host) %> |
+ <%= render Admin::Tenants::ToggleHiddenComponent.new(tenant) %> |
+
+ <%= render Admin::TableActionsComponent.new(tenant, actions: [:edit]) %>
+ |
+
diff --git a/app/components/admin/tenants/row_component.rb b/app/components/admin/tenants/row_component.rb
new file mode 100644
index 000000000..648b0db7a
--- /dev/null
+++ b/app/components/admin/tenants/row_component.rb
@@ -0,0 +1,7 @@
+class Admin::Tenants::RowComponent < ApplicationComponent
+ attr_reader :tenant
+
+ def initialize(tenant)
+ @tenant = tenant
+ end
+end
diff --git a/app/components/admin/tenants/toggle_hidden_component.html.erb b/app/components/admin/tenants/toggle_hidden_component.html.erb
new file mode 100644
index 000000000..64a3a2e93
--- /dev/null
+++ b/app/components/admin/tenants/toggle_hidden_component.html.erb
@@ -0,0 +1 @@
+<%= render Admin::ToggleSwitchComponent.new(action, tenant, pressed: enabled?, **options) %>
diff --git a/app/components/admin/tenants/toggle_hidden_component.rb b/app/components/admin/tenants/toggle_hidden_component.rb
new file mode 100644
index 000000000..12488ce21
--- /dev/null
+++ b/app/components/admin/tenants/toggle_hidden_component.rb
@@ -0,0 +1,28 @@
+class Admin::Tenants::ToggleHiddenComponent < ApplicationComponent
+ attr_reader :tenant
+
+ def initialize(tenant)
+ @tenant = tenant
+ end
+
+ private
+
+ def action
+ if enabled?
+ :hide
+ else
+ :restore
+ end
+ end
+
+ def options
+ {
+ method: :put,
+ "aria-label": t("admin.tenants.index.enable", tenant: tenant.name)
+ }
+ end
+
+ def enabled?
+ !tenant.hidden?
+ end
+end
diff --git a/app/controllers/admin/tenants_controller.rb b/app/controllers/admin/tenants_controller.rb
index a43e88166..e7d6e949a 100644
--- a/app/controllers/admin/tenants_controller.rb
+++ b/app/controllers/admin/tenants_controller.rb
@@ -27,6 +27,24 @@ class Admin::TenantsController < Admin::BaseController
end
end
+ def hide
+ @tenant.hide
+
+ respond_to do |format|
+ format.html { redirect_to admin_tenants_path, notice: t("admin.tenants.hide.notice") }
+ format.js { render template: "admin/tenants/toggle_enabled" }
+ end
+ end
+
+ def restore
+ @tenant.restore
+
+ respond_to do |format|
+ format.html { redirect_to admin_tenants_path, notice: t("admin.tenants.restore.notice") }
+ format.js { render template: "admin/tenants/toggle_enabled" }
+ end
+ end
+
private
def tenant_params
diff --git a/app/models/abilities/administrator.rb b/app/models/abilities/administrator.rb
index 9e3556e80..f874af58b 100644
--- a/app/models/abilities/administrator.rb
+++ b/app/models/abilities/administrator.rb
@@ -136,7 +136,7 @@ module Abilities
can [:create, :read], LocalCensusRecords::Import
if Rails.application.config.multitenancy && Tenant.default?
- can [:create, :read, :update], Tenant
+ can [:create, :read, :update, :hide, :restore], Tenant
end
end
end
diff --git a/app/models/tenant.rb b/app/models/tenant.rb
index ffbba3442..654cbecf0 100644
--- a/app/models/tenant.rb
+++ b/app/models/tenant.rb
@@ -12,6 +12,8 @@ class Tenant < ApplicationRecord
after_update :rename_schema
after_destroy :destroy_schema
+ scope :only_hidden, -> { where.not(hidden_at: nil) }
+
def self.find_by_domain(host)
domain.find_by(schema: host)
end
@@ -20,6 +22,16 @@ class Tenant < ApplicationRecord
return nil unless Rails.application.config.multitenancy.present?
return nil if host.blank? || host.match?(Resolv::AddressRegex)
+ schema = schema_for(host)
+
+ if schema && only_hidden.find_by(schema: schema)
+ raise Apartment::TenantNotFound
+ else
+ schema
+ end
+ end
+
+ def self.schema_for(host)
host_without_www = host.delete_prefix("www.")
if find_by_domain(host)
@@ -137,6 +149,18 @@ class Tenant < ApplicationRecord
self.class.host_for(schema)
end
+ def hide
+ update_attribute(:hidden_at, Time.current)
+ end
+
+ def restore
+ update_attribute(:hidden_at, nil)
+ end
+
+ def hidden?
+ hidden_at.present?
+ end
+
private
def create_schema
diff --git a/app/views/admin/tenants/toggle_enabled.js.erb b/app/views/admin/tenants/toggle_enabled.js.erb
new file mode 100644
index 000000000..8e2a0eadc
--- /dev/null
+++ b/app/views/admin/tenants/toggle_enabled.js.erb
@@ -0,0 +1,4 @@
+var replacement = $("<%= j render Admin::Tenants::RowComponent.new(@tenant) %>");
+var row = $("#<%= dom_id(@tenant) %>");
+
+row.html(replacement.html()).find(".toggle-switch [type='submit']").focus();
diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml
index 4f59150ad..b128ec8cf 100644
--- a/config/locales/en/admin.yml
+++ b/config/locales/en/admin.yml
@@ -1635,10 +1635,16 @@ en:
form:
use_subdomain: "Use a subdomain in the %{domain} domain to access this tenant"
use_domain: "Use a different domain to access this tenant"
+ hide:
+ notice: Tenant disabled successfully
index:
create: Create tenant
+ enable: "Enable tenant %{tenant}"
+ enabled: Enabled
new:
title: New tenant
+ restore:
+ notice: Tenant enabled successfully
update:
notice: Tenant updated successfully
homepage:
diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml
index 948c6cf5c..509748e57 100644
--- a/config/locales/es/admin.yml
+++ b/config/locales/es/admin.yml
@@ -1634,10 +1634,16 @@ es:
form:
use_subdomain: "Utiliza un subdominio en el dominio %{domain} para acceder a esta entidad"
use_domain: "Utiliza un dominio distinto para acceder a esta entidad"
+ hide:
+ notice: Entidad deshabilitada correctamente
index:
create: Crear entidad
+ enable: "Habilitar entidad %{tenant}"
+ enabled: Habilitada
new:
title: Nueva entidad
+ restore:
+ notice: Entidad habilitada correctamente
update:
notice: Entidad actualizada correctamente
homepage:
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index af7d96cec..2fa509e36 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -284,7 +284,12 @@ namespace :admin do
delete :cancel, on: :collection
end
- resources :tenants, except: [:show, :destroy]
+ resources :tenants, except: [:show, :destroy] do
+ member do
+ put :hide
+ put :restore
+ end
+ end
end
resolve "Milestone" do |milestone|
diff --git a/db/migrate/20221203140136_add_hidden_at_to_tenants.rb b/db/migrate/20221203140136_add_hidden_at_to_tenants.rb
new file mode 100644
index 000000000..5e2006313
--- /dev/null
+++ b/db/migrate/20221203140136_add_hidden_at_to_tenants.rb
@@ -0,0 +1,5 @@
+class AddHiddenAtToTenants < ActiveRecord::Migration[6.0]
+ def change
+ add_column :tenants, :hidden_at, :datetime
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index f12f53061..be20fc451 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2022_11_20_123254) do
+ActiveRecord::Schema.define(version: 2022_12_03_140136) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
@@ -1562,6 +1562,7 @@ ActiveRecord::Schema.define(version: 2022_11_20_123254) do
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "schema_type", default: 0, null: false
+ t.datetime "hidden_at"
t.index ["name"], name: "index_tenants_on_name", unique: true
t.index ["schema"], name: "index_tenants_on_schema", unique: true
end
diff --git a/spec/models/abilities/administrator_spec.rb b/spec/models/abilities/administrator_spec.rb
index a34ec08e6..956fed48c 100644
--- a/spec/models/abilities/administrator_spec.rb
+++ b/spec/models/abilities/administrator_spec.rb
@@ -181,6 +181,8 @@ describe Abilities::Administrator do
it { should be_able_to :create, Tenant }
it { should be_able_to :read, Tenant }
it { should be_able_to :update, Tenant }
+ it { should be_able_to :hide, Tenant }
+ it { should be_able_to :restore, Tenant }
it { should_not be_able_to :destroy, Tenant }
context "administrators from other tenants" do
@@ -193,6 +195,8 @@ describe Abilities::Administrator do
it { should_not be_able_to :read, Tenant }
it { should_not be_able_to :update, Tenant }
it { should_not be_able_to :destroy, Tenant }
+ it { should_not be_able_to :hide, Tenant }
+ it { should_not be_able_to :restore, Tenant }
end
end
end
diff --git a/spec/models/tenant_spec.rb b/spec/models/tenant_spec.rb
index b15dc7560..ba94f001a 100644
--- a/spec/models/tenant_spec.rb
+++ b/spec/models/tenant_spec.rb
@@ -117,6 +117,37 @@ describe Tenant do
expect(Tenant.resolve_host("www.consul.dev")).to eq "www.consul.dev"
end
+ it "raises an exception when accessing a hidden tenant using a subdomain" do
+ insert(:tenant, schema: "saturn", hidden_at: Time.current)
+
+ expect { Tenant.resolve_host("saturn.consul.dev") }.to raise_exception(Apartment::TenantNotFound)
+ end
+
+ it "raises an exception when accessing a hidden tenant using a domain" do
+ insert(:tenant, :domain, schema: "consul.dev", hidden_at: Time.current)
+
+ expect { Tenant.resolve_host("consul.dev") }.to raise_exception(Apartment::TenantNotFound)
+ end
+
+ it "raises an exception when accessing a hidden tenant using a domain starting with www" do
+ insert(:tenant, :domain, schema: "www.consul.dev", hidden_at: Time.current)
+
+ expect { Tenant.resolve_host("www.consul.dev") }.to raise_exception(Apartment::TenantNotFound)
+ end
+
+ it "raises an exception when accessing a hidden tenant with a domain and another tenant resolves to the same domain" do
+ insert(:tenant, :domain, schema: "saturn.consul.dev", hidden_at: Time.current)
+ insert(:tenant, schema: "saturn")
+
+ expect { Tenant.resolve_host("saturn.consul.dev") }.to raise_exception(Apartment::TenantNotFound)
+ end
+
+ it "ignores hidden tenants with nil as their schema" do
+ insert(:tenant, schema: nil, hidden_at: Time.current)
+
+ expect(Tenant.resolve_host("consul.dev")).to be nil
+ end
+
context "multitenancy disabled" do
before { allow(Rails.application.config).to receive(:multitenancy).and_return(false) }
@@ -318,6 +349,11 @@ describe Tenant do
expect(build(:tenant, schema: "subdomainx")).not_to be_valid
end
+ it "is not valid with the schema of an already existing hidden record" do
+ insert(:tenant, schema: "subdomainx", hidden_at: Time.current)
+ expect(build(:tenant, schema: "subdomainx")).not_to be_valid
+ end
+
it "is not valid with an excluded subdomain" do
%w[mail public shared_extensions www].each do |subdomain|
tenant.schema = subdomain
@@ -345,6 +381,11 @@ describe Tenant do
expect(build(:tenant, name: "Name X")).not_to be_valid
end
+ it "is not valid with the name of an already existing hidden record" do
+ insert(:tenant, name: "Name X", hidden_at: Time.current)
+ expect(build(:tenant, name: "Name X")).not_to be_valid
+ end
+
context "Domain schema type" do
before { tenant.schema_type = :domain }
diff --git a/spec/system/admin/tenants_spec.rb b/spec/system/admin/tenants_spec.rb
index 92096261b..38389dbe8 100644
--- a/spec/system/admin/tenants_spec.rb
+++ b/spec/system/admin/tenants_spec.rb
@@ -63,4 +63,43 @@ describe "Tenants", :admin, :seed_tenants do
expect(page).to have_current_path root_path
expect(page).to have_link "Sign in"
end
+
+ scenario "Hide and restore", :show_exceptions do
+ create(:tenant, schema: "moon", name: "Moon")
+
+ visit admin_tenants_path
+
+ within("tr", text: "moon") do
+ expect(page).to have_content "Yes"
+
+ click_button "Enable tenant Moon"
+
+ expect(page).to have_content "No"
+ expect(page).not_to have_link "moon.lvh.me"
+ end
+
+ with_subdomain("moon") do
+ visit root_path
+
+ expect(page).to have_title "Not found"
+ end
+
+ visit admin_tenants_path
+
+ within("tr", text: "moon") do
+ expect(page).to have_content "No"
+
+ click_button "Enable tenant Moon"
+
+ expect(page).to have_content "Yes"
+ expect(page).to have_link "moon.lvh.me"
+ end
+
+ with_subdomain("moon") do
+ visit root_path
+
+ expect(page).to have_link "Sign in"
+ expect(page).not_to have_title "Not found"
+ end
+ end
end