diff --git a/app/assets/stylesheets/admin/menu.scss b/app/assets/stylesheets/admin/menu.scss index 4228ea197..ce2c0f41c 100644 --- a/app/assets/stylesheets/admin/menu.scss +++ b/app/assets/stylesheets/admin/menu.scss @@ -109,6 +109,14 @@ &.ml-link { @include icon(brain, solid); } + + &.administrators-link { + @include icon(user, solid); + } + + &.tenants-link { + @include icon(building, regular); + } } li { diff --git a/app/components/admin/menu_component.rb b/app/components/admin/menu_component.rb index dba560278..d25e0860f 100644 --- a/app/components/admin/menu_component.rb +++ b/app/components/admin/menu_component.rb @@ -3,28 +3,40 @@ class Admin::MenuComponent < ApplicationComponent use_helpers :can? def links - [ - (proposals_link if feature?(:proposals)), - (debates_link if feature?(:debates)), - comments_link, - (polls_link if feature?(:polls)), - (legislation_link if feature?(:legislation)), - (budgets_link if feature?(:budgets)), - booths_links, - (signature_sheets_link if feature?(:signature_sheets)), - messages_links, - site_customization_links, - moderated_content_links, - profiles_links, - stats_link, - settings_links, - dashboard_links, - (machine_learning_link if ::MachineLearning.enabled?) - ] + if Rails.application.multitenancy_management_mode? + multitenancy_management_links + else + default_links + end end private + def default_links + [ + (proposals_link if feature?(:proposals)), + (debates_link if feature?(:debates)), + comments_link, + (polls_link if feature?(:polls)), + (legislation_link if feature?(:legislation)), + (budgets_link if feature?(:budgets)), + booths_links, + (signature_sheets_link if feature?(:signature_sheets)), + messages_links, + site_customization_links, + moderated_content_links, + profiles_links, + stats_link, + settings_links, + dashboard_links, + (machine_learning_link if ::MachineLearning.enabled?) + ] + end + + def multitenancy_management_links + [tenants_link, administrators_link] + end + def moderated_content? moderated_sections.include?(controller_name) && controller.class.module_parent != Admin::Legislation end @@ -395,7 +407,8 @@ class Admin::MenuComponent < ApplicationComponent [ t("admin.menu.administrators"), admin_administrators_path, - controller_name == "administrators" + controller_name == "administrators", + class: "administrators-link" ] end @@ -482,7 +495,8 @@ class Admin::MenuComponent < ApplicationComponent [ t("admin.menu.multitenancy"), admin_tenants_path, - controller_name == "tenants" + controller_name == "tenants", + class: "tenants-link" ] end end diff --git a/app/components/layout/admin_header_component.html.erb b/app/components/layout/admin_header_component.html.erb index 9975ed46d..99d48e18a 100644 --- a/app/components/layout/admin_header_component.html.erb +++ b/app/components/layout/admin_header_component.html.erb @@ -2,8 +2,10 @@ diff --git a/app/components/layout/admin_header_component.rb b/app/components/layout/admin_header_component.rb index f28690c53..0320e84e8 100644 --- a/app/components/layout/admin_header_component.rb +++ b/app/components/layout/admin_header_component.rb @@ -29,4 +29,8 @@ class Layout::AdminHeaderComponent < ApplicationComponent def show_account_menu? show_admin_menu?(user) || namespace != "management" end + + def show_link_to_root_path? + !Rails.application.multitenancy_management_mode? + end end diff --git a/app/components/layout/admin_login_items_component.rb b/app/components/layout/admin_login_items_component.rb index 91f0fd7db..e19ad433d 100644 --- a/app/components/layout/admin_login_items_component.rb +++ b/app/components/layout/admin_login_items_component.rb @@ -7,7 +7,7 @@ class Layout::AdminLoginItemsComponent < ApplicationComponent end def render? - show_admin_menu?(user) + show_admin_menu?(user) && !Rails.application.multitenancy_management_mode? end private diff --git a/app/components/layout/footer_component.rb b/app/components/layout/footer_component.rb index 555dde0d8..49d6d59f2 100644 --- a/app/components/layout/footer_component.rb +++ b/app/components/layout/footer_component.rb @@ -1,6 +1,10 @@ class Layout::FooterComponent < ApplicationComponent use_helpers :content_block + def render? + !Rails.application.multitenancy_management_mode? + end + def footer_legal_content_block content_block("footer_legal") end diff --git a/app/components/layout/login_items_component.html.erb b/app/components/layout/login_items_component.html.erb index 048ef300d..73220dffb 100644 --- a/app/components/layout/login_items_component.html.erb +++ b/app/components/layout/login_items_component.html.erb @@ -1,12 +1,14 @@ <% if user %> -
  • - <%= layout_menu_link_to t("layouts.header.my_activity_link"), - user_path(user), - controller_name == "users", - rel: "nofollow", - title: t("shared.go_to_page") + - t("layouts.header.my_activity_link") %> -
  • + <% if show_my_activity_link? %> +
  • + <%= layout_menu_link_to t("layouts.header.my_activity_link"), + user_path(user), + controller_name == "users", + rel: "nofollow", + title: t("shared.go_to_page") + + t("layouts.header.my_activity_link") %> +
  • + <% end %>
  • <%= layout_menu_link_to t("layouts.header.my_account_link"), account_path, diff --git a/app/components/layout/login_items_component.rb b/app/components/layout/login_items_component.rb index 8c1e79bce..adbe892c7 100644 --- a/app/components/layout/login_items_component.rb +++ b/app/components/layout/login_items_component.rb @@ -5,4 +5,10 @@ class Layout::LoginItemsComponent < ApplicationComponent def initialize(user) @user = user end + + private + + def show_my_activity_link? + !Rails.application.multitenancy_management_mode? + end end diff --git a/app/components/layout/notification_item_component.html.erb b/app/components/layout/notification_item_component.html.erb index 8be3090d3..3b96d2454 100644 --- a/app/components/layout/notification_item_component.html.erb +++ b/app/components/layout/notification_item_component.html.erb @@ -1,12 +1,10 @@ -<% if user %> -
  • - <%= link_to notifications_path, rel: "nofollow", - title: text, - class: "notifications #{notifications_class}" do %> - - <%= t("layouts.header.notification_item.notifications") %> - - <%= text %> - <% end %> -
  • -<% end %> +
  • + <%= link_to notifications_path, rel: "nofollow", + title: text, + class: "notifications #{notifications_class}" do %> + + <%= t("layouts.header.notification_item.notifications") %> + + <%= text %> + <% end %> +
  • diff --git a/app/components/layout/notification_item_component.rb b/app/components/layout/notification_item_component.rb index c620180b0..6b30f4ab4 100644 --- a/app/components/layout/notification_item_component.rb +++ b/app/components/layout/notification_item_component.rb @@ -5,6 +5,10 @@ class Layout::NotificationItemComponent < ApplicationComponent @user = user end + def render? + user.present? && !Rails.application.multitenancy_management_mode? + end + private def text diff --git a/app/components/layout/subnavigation_component.rb b/app/components/layout/subnavigation_component.rb index 6da3b320b..5fdaa0c49 100644 --- a/app/components/layout/subnavigation_component.rb +++ b/app/components/layout/subnavigation_component.rb @@ -1,3 +1,7 @@ class Layout::SubnavigationComponent < ApplicationComponent use_helpers :content_block, :layout_menu_link_to + + def render? + !Rails.application.multitenancy_management_mode? + end end diff --git a/app/controllers/concerns/access_denied_handler.rb b/app/controllers/concerns/access_denied_handler.rb index 080a77283..95edbbfe9 100644 --- a/app/controllers/concerns/access_denied_handler.rb +++ b/app/controllers/concerns/access_denied_handler.rb @@ -4,7 +4,13 @@ module AccessDeniedHandler included do rescue_from CanCan::AccessDenied do |exception| respond_to do |format| - format.html { redirect_to main_app.root_path, alert: exception.message } + format.html do + if Rails.application.multitenancy_management_mode? + redirect_to main_app.account_path, alert: exception.message + else + redirect_to main_app.root_path, alert: exception.message + end + end format.json { render json: { error: exception.message }, status: :forbidden } end end diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb index 338f3ee0d..98306b99f 100644 --- a/app/controllers/users/sessions_controller.rb +++ b/app/controllers/users/sessions_controller.rb @@ -7,7 +7,9 @@ class Users::SessionsController < Devise::SessionsController private def after_sign_in_path_for(resource) - if !verifying_via_email? && resource.show_welcome_screen? + if Rails.application.multitenancy_management_mode? && !resource.administrator? + account_path + elsif !verifying_via_email? && resource.show_welcome_screen? welcome_path else super diff --git a/config/application.rb b/config/application.rb index 3411e15e0..593c41866 100644 --- a/config/application.rb +++ b/config/application.rb @@ -159,6 +159,12 @@ module Consul # Set to true to enable managing different tenants using the same application config.multitenancy = Rails.application.secrets.multitenancy + # Set to true if you want that the default tenant only to be used to manage other tenants + config.multitenancy_management_mode = Rails.application.secrets.multitenancy_management_mode + + def multitenancy_management_mode? + config.multitenancy && Tenant.default? && config.multitenancy_management_mode + end end end diff --git a/config/routes.rb b/config/routes.rb index 6359ad589..969150e6a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -6,44 +6,82 @@ Rails.application.routes.draw do draw :account draw :admin - draw :budget - draw :comment - draw :community - draw :debate draw :devise - draw :direct_upload - draw :document - draw :graphql - draw :legislation - draw :management - draw :moderation - draw :notification - draw :officing - draw :poll - draw :proposal - draw :related_content - draw :sdg - draw :sdg_management - draw :tag - draw :user - draw :valuation - draw :verification - root "welcome#index" - get "/welcome", to: "welcome#welcome" - get "/consul.json", to: "installation#details" - get "robots.txt", to: "robots#index" + constraints lambda { |request| Rails.application.multitenancy_management_mode? } do + get "/", to: "admin/tenants#index" + end - resources :images, only: [:destroy] - resources :documents, only: [:destroy] - resources :follows, only: [:create, :destroy] - resources :remote_translations, only: [:create] + constraints lambda { |request| !Rails.application.multitenancy_management_mode? } do + draw :budget + draw :comment + draw :community + draw :debate + draw :direct_upload + draw :document + draw :graphql + draw :legislation + draw :management + draw :moderation + draw :notification + draw :officing + draw :poll + draw :proposal + draw :related_content + draw :sdg + draw :sdg_management + draw :tag + draw :user + draw :valuation + draw :verification - # More info pages - get "help", to: "pages#show", id: "help/index", as: "help" - get "help/how-to-use", to: "pages#show", id: "help/how_to_use/index", as: "how_to_use" - get "help/faq", to: "pages#show", id: "faq", as: "faq" + root "welcome#index" + get "/welcome", to: "welcome#welcome" + get "/consul.json", to: "installation#details" + get "robots.txt", to: "robots#index" - # Static pages - resources :pages, path: "/", only: [:show] + resources :images, only: [:destroy] + resources :documents, only: [:destroy] + resources :follows, only: [:create, :destroy] + resources :remote_translations, only: [:create] + + # More info pages + get "help", to: "pages#show", id: "help/index", as: "help" + get "help/how-to-use", to: "pages#show", id: "help/how_to_use/index", as: "how_to_use" + get "help/faq", to: "pages#show", id: "faq", as: "faq" + + # Static pages + resources :pages, path: "/", only: [:show] + end + + resolve "Budget::Investment" do |investment, options| + [investment.budget, :investment, options.merge(id: investment)] + end + + resolve("Topic") { |topic, options| [topic.community, topic, options] } + + resolve "Legislation::Proposal" do |proposal, options| + [proposal.process, :proposal, options.merge(id: proposal)] + end + + resolve "Vote" do |vote, options| + [*resource_hierarchy_for(vote.votable), vote, options] + end + + resolve "Legislation::Question" do |question, options| + [question.process, :question, options.merge(id: question)] + end + + resolve "Legislation::Annotation" do |annotation, options| + [annotation.draft_version.process, :draft_version, :annotation, + options.merge(draft_version_id: annotation.draft_version, id: annotation)] + end + + resolve "Poll::Question" do |question, options| + [:question, options.merge(id: question)] + end + + resolve "SDG::LocalTarget" do |target, options| + [:local_target, options.merge(id: target)] + end end diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 9709300d3..ed300820a 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -1,307 +1,310 @@ namespace :admin do root to: "dashboard#index" - resources :organizations, only: :index do - get :search, on: :collection - member do - put :verify - put :reject - end - end - - resources :hidden_users, only: [:index, :show] do - member do - put :restore - put :confirm_hide - end - end - - resources :hidden_budget_investments, only: :index do - member do - put :restore - put :confirm_hide - end - end - - resources :hidden_debates, only: :index do - member do - put :restore - put :confirm_hide - end - end - - resources :debates, only: [:index, :show] - - resources :proposals, only: [:index, :show, :update] do - member do - patch :select - patch :deselect - end - - resources :milestones, controller: "proposal_milestones" - resources :progress_bars, except: :show, controller: "proposal_progress_bars" - end - - resources :hidden_proposals, only: :index do - member do - put :restore - put :confirm_hide - end - end - - resources :hidden_proposal_notifications, only: :index do - member do - put :restore - put :confirm_hide - end - end - - resources :budgets, except: [:create, :new] do - member do - patch :publish - put :calculate_winners - end - - resources :groups, except: [:index, :show], controller: "budget_groups" do - resources :headings, except: [:index, :show], controller: "budget_headings" - end - - resources :budget_investments, only: [:index, :show, :edit, :update] do - member do - patch :select - patch :deselect - patch :show_to_valuators - patch :hide_from_valuators - end - - resources :audits, only: :show, controller: "budget_investment_audits" - resources :milestones, controller: "budget_investment_milestones" - resources :progress_bars, except: :show, controller: "budget_investment_progress_bars" - end - - resources :budget_phases, only: [:edit, :update] do - member do - patch :enable - patch :disable - end - end - end - - namespace :budgets_wizard do - resources :budgets, only: [:create, :new, :edit, :update] do - resources :groups, only: [:index, :create, :edit, :update, :destroy] do - resources :headings, only: [:index, :create, :edit, :update, :destroy] - end - - resources :phases, as: "budget_phases", only: [:index, :edit, :update] do - member do - patch :enable - patch :disable - end - end - end - end - - resources :milestone_statuses, only: [:index, :new, :create, :update, :edit, :destroy] - - resources :signature_sheets, only: [:index, :new, :create, :show] - - resources :banners, only: [:index, :new, :create, :edit, :update, :destroy] do - collection { get :search } - end - - resources :hidden_comments, only: :index do - member do - put :restore - put :confirm_hide - end - end - - resources :comments, only: :index - - resources :tags, only: [:index, :create, :update, :destroy] - - resources :officials, only: [:index, :edit, :update, :destroy] do - get :search, on: :collection - end - - resources :settings, only: [:index, :update] - put :update_map, to: "settings#update_map" - put :update_content_types, to: "settings#update_content_types" - - resources :moderators, only: [:index, :create, :destroy] do - get :search, on: :collection - end - - resources :valuators, only: [:show, :index, :edit, :update, :create, :destroy] do - get :search, on: :collection - get :summary, on: :collection - end - - resources :valuator_groups - - resources :managers, only: [:index, :create, :destroy] do - get :search, on: :collection - end - - namespace :sdg do - resources :managers, only: [:index, :create, :destroy] - end resources :administrators, only: [:index, :create, :destroy, :edit, :update] do get :search, on: :collection end - resources :users, only: [:index, :show] - - scope module: :poll do - resources :polls do - get :booth_assignments, on: :collection - - resources :booth_assignments, only: [:index, :show, :create, :destroy] do - get :search_booths, on: :collection - get :manage, on: :collection - end - - resources :officer_assignments, only: [:index, :create, :destroy] do - get :search_officers, on: :collection - get :by_officer, on: :collection - end - - resources :recounts, only: :index - resources :results, only: :index - end - - resources :officers, only: [:index, :new, :create, :destroy] do - get :search, on: :collection - end - - resources :booths do - get :available, on: :collection - - resources :shifts do - get :search_officers, on: :collection - end - end - - resources :questions, shallow: true do - resources :options, except: [:index, :show], controller: "questions/options", shallow: false - resources :options, only: [], controller: "questions/options" do - resources :images, controller: "questions/options/images" - resources :videos, controller: "questions/options/videos", shallow: false - resources :documents, only: [:index, :create], controller: "questions/options/documents" - end - post "/options/order_options", to: "questions/options#order_options" - end - - resource :active_polls, only: [:create, :edit, :update] - end - - resources :verifications, controller: :verifications, only: :index do - get :search, on: :collection - end - - resource :activity, controller: :activity, only: :show - - resources :newsletters do - member do - post :deliver - end - get :users, on: :collection - end - - resources :admin_notifications do - member do - post :deliver - end - end - - resources :system_emails, only: [:index] do - get :view - get :preview_pending - put :moderate_pending - put :send_pending - end - - resources :emails_download, only: :index do - get :generate_csv, on: :collection - end - - resource :stats, only: :show do - get :graph, on: :member - get :budgets, on: :collection - get :budget_supporting, on: :member - get :budget_balloting, on: :member - get :proposal_notifications, on: :collection - get :direct_messages, on: :collection - get :polls, on: :collection - get :sdg, on: :collection - end - - namespace :legislation do - resources :processes do - resources :questions - resources :proposals do - member do - patch :select - patch :deselect - end - end - resources :draft_versions - resources :milestones - resources :progress_bars, except: :show - resource :homepage, only: [:edit, :update] - end - end - - resources :geozones, only: [:index, :new, :create, :edit, :update, :destroy] - resource :locales, only: [:show, :update] - - namespace :site_customization do - resources :pages, except: [:show] do - resources :cards, except: [:show], as: :widget_cards - end - resources :images, only: [:index, :update, :destroy] - resources :content_blocks, except: [:show] - delete "/heading_content_blocks/:id", to: "content_blocks#delete_heading_content_block", - as: "delete_heading_content_block" - get "/edit_heading_content_blocks/:id", to: "content_blocks#edit_heading_content_block", - as: "edit_heading_content_block" - put "/update_heading_content_blocks/:id", to: "content_blocks#update_heading_content_block", - as: "update_heading_content_block" - resources :information_texts, only: [:index] do - post :update, on: :collection - end - resources :documents, only: [:index, :new, :create, :destroy] - end - - resource :homepage, controller: :homepage, only: [:show] - - namespace :widget do - resources :cards - resources :feeds, only: [:update] - end - - namespace :dashboard do - resources :actions, only: [:index, :new, :create, :edit, :update, :destroy] - resources :administrator_tasks, only: [:index, :edit, :update] - end - - resources :local_census_records - namespace :local_census_records do - resources :imports, only: [:new, :create, :show] - end - - resource :machine_learning, controller: :machine_learning, only: [:show] do - post :execute, on: :collection - delete :cancel, on: :collection - end - resources :tenants, except: [:show, :destroy] do member do put :hide put :restore end end + + constraints lambda { |request| !Rails.application.multitenancy_management_mode? } do + resources :organizations, only: :index do + get :search, on: :collection + member do + put :verify + put :reject + end + end + + resources :hidden_users, only: [:index, :show] do + member do + put :restore + put :confirm_hide + end + end + + resources :hidden_budget_investments, only: :index do + member do + put :restore + put :confirm_hide + end + end + + resources :hidden_debates, only: :index do + member do + put :restore + put :confirm_hide + end + end + + resources :debates, only: [:index, :show] + + resources :proposals, only: [:index, :show, :update] do + member do + patch :select + patch :deselect + end + + resources :milestones, controller: "proposal_milestones" + resources :progress_bars, except: :show, controller: "proposal_progress_bars" + end + + resources :hidden_proposals, only: :index do + member do + put :restore + put :confirm_hide + end + end + + resources :hidden_proposal_notifications, only: :index do + member do + put :restore + put :confirm_hide + end + end + + resources :budgets, except: [:create, :new] do + member do + patch :publish + put :calculate_winners + end + + resources :groups, except: [:index, :show], controller: "budget_groups" do + resources :headings, except: [:index, :show], controller: "budget_headings" + end + + resources :budget_investments, only: [:index, :show, :edit, :update] do + member do + patch :select + patch :deselect + patch :show_to_valuators + patch :hide_from_valuators + end + + resources :audits, only: :show, controller: "budget_investment_audits" + resources :milestones, controller: "budget_investment_milestones" + resources :progress_bars, except: :show, controller: "budget_investment_progress_bars" + end + + resources :budget_phases, only: [:edit, :update] do + member do + patch :enable + patch :disable + end + end + end + + namespace :budgets_wizard do + resources :budgets, only: [:create, :new, :edit, :update] do + resources :groups, only: [:index, :create, :edit, :update, :destroy] do + resources :headings, only: [:index, :create, :edit, :update, :destroy] + end + + resources :phases, as: "budget_phases", only: [:index, :edit, :update] do + member do + patch :enable + patch :disable + end + end + end + end + + resources :milestone_statuses, only: [:index, :new, :create, :update, :edit, :destroy] + + resources :signature_sheets, only: [:index, :new, :create, :show] + + resources :banners, only: [:index, :new, :create, :edit, :update, :destroy] do + collection { get :search } + end + + resources :hidden_comments, only: :index do + member do + put :restore + put :confirm_hide + end + end + + resources :comments, only: :index + + resources :tags, only: [:index, :create, :update, :destroy] + + resources :officials, only: [:index, :edit, :update, :destroy] do + get :search, on: :collection + end + + resources :settings, only: [:index, :update] + put :update_map, to: "settings#update_map" + put :update_content_types, to: "settings#update_content_types" + + resources :moderators, only: [:index, :create, :destroy] do + get :search, on: :collection + end + + resources :valuators, only: [:show, :index, :edit, :update, :create, :destroy] do + get :search, on: :collection + get :summary, on: :collection + end + + resources :valuator_groups + + resources :managers, only: [:index, :create, :destroy] do + get :search, on: :collection + end + + namespace :sdg do + resources :managers, only: [:index, :create, :destroy] + end + + resources :users, only: [:index, :show] + + scope module: :poll do + resources :polls do + get :booth_assignments, on: :collection + + resources :booth_assignments, only: [:index, :show, :create, :destroy] do + get :search_booths, on: :collection + get :manage, on: :collection + end + + resources :officer_assignments, only: [:index, :create, :destroy] do + get :search_officers, on: :collection + get :by_officer, on: :collection + end + + resources :recounts, only: :index + resources :results, only: :index + end + + resources :officers, only: [:index, :new, :create, :destroy] do + get :search, on: :collection + end + + resources :booths do + get :available, on: :collection + + resources :shifts do + get :search_officers, on: :collection + end + end + + resources :questions, shallow: true do + resources :options, except: [:index, :show], controller: "questions/options", shallow: false + resources :options, only: [], controller: "questions/options" do + resources :images, controller: "questions/options/images" + resources :videos, controller: "questions/options/videos", shallow: false + resources :documents, only: [:index, :create], controller: "questions/options/documents" + end + post "/options/order_options", to: "questions/options#order_options" + end + + resource :active_polls, only: [:create, :edit, :update] + end + + resources :verifications, controller: :verifications, only: :index do + get :search, on: :collection + end + + resource :activity, controller: :activity, only: :show + + resources :newsletters do + member do + post :deliver + end + get :users, on: :collection + end + + resources :admin_notifications do + member do + post :deliver + end + end + + resources :system_emails, only: [:index] do + get :view + get :preview_pending + put :moderate_pending + put :send_pending + end + + resources :emails_download, only: :index do + get :generate_csv, on: :collection + end + + resource :stats, only: :show do + get :graph, on: :member + get :budgets, on: :collection + get :budget_supporting, on: :member + get :budget_balloting, on: :member + get :proposal_notifications, on: :collection + get :direct_messages, on: :collection + get :polls, on: :collection + get :sdg, on: :collection + end + + namespace :legislation do + resources :processes do + resources :questions + resources :proposals do + member do + patch :select + patch :deselect + end + end + resources :draft_versions + resources :milestones + resources :progress_bars, except: :show + resource :homepage, only: [:edit, :update] + end + end + + resources :geozones, only: [:index, :new, :create, :edit, :update, :destroy] + resource :locales, only: [:show, :update] + + namespace :site_customization do + resources :pages, except: [:show] do + resources :cards, except: [:show], as: :widget_cards + end + resources :images, only: [:index, :update, :destroy] + resources :content_blocks, except: [:show] + delete "/heading_content_blocks/:id", to: "content_blocks#delete_heading_content_block", + as: "delete_heading_content_block" + get "/edit_heading_content_blocks/:id", to: "content_blocks#edit_heading_content_block", + as: "edit_heading_content_block" + put "/update_heading_content_blocks/:id", to: "content_blocks#update_heading_content_block", + as: "update_heading_content_block" + resources :information_texts, only: [:index] do + post :update, on: :collection + end + resources :documents, only: [:index, :new, :create, :destroy] + end + + resource :homepage, controller: :homepage, only: [:show] + + namespace :widget do + resources :cards + resources :feeds, only: [:update] + end + + namespace :dashboard do + resources :actions, only: [:index, :new, :create, :edit, :update, :destroy] + resources :administrator_tasks, only: [:index, :edit, :update] + end + + resources :local_census_records + namespace :local_census_records do + resources :imports, only: [:new, :create, :show] + end + + resource :machine_learning, controller: :machine_learning, only: [:show] do + post :execute, on: :collection + delete :cancel, on: :collection + end + end end resolve "Milestone" do |milestone| diff --git a/config/routes/budget.rb b/config/routes/budget.rb index f6054d95f..5dd733e83 100644 --- a/config/routes/budget.rb +++ b/config/routes/budget.rb @@ -19,7 +19,3 @@ resources :budgets, only: [:show, :index] do resource :stats, only: :show, controller: "budgets/stats" resource :executions, only: :show, controller: "budgets/executions" end - -resolve "Budget::Investment" do |investment, options| - [investment.budget, :investment, options.merge(id: investment)] -end diff --git a/config/routes/community.rb b/config/routes/community.rb index ca41ee2d1..9e010ffb7 100644 --- a/config/routes/community.rb +++ b/config/routes/community.rb @@ -1,5 +1,3 @@ resources :communities, only: [:show] do resources :topics end - -resolve("Topic") { |topic, options| [topic.community, topic, options] } diff --git a/config/routes/custom.rb b/config/routes/custom.rb index 8c5264b50..28dc00ba6 100644 --- a/config/routes/custom.rb +++ b/config/routes/custom.rb @@ -26,3 +26,12 @@ # over the default routes. So, if you define a route for `/proposals`, # the default action for `/proposals` will not be used and the one you # define will be used instead. + +constraints lambda { |request| !Rails.application.multitenancy_management_mode? } do + # The routes defined within this block will not be accessible if multitenancy + # management mode is enabled. If you need these routes to be accessible when + # using multitenancy management mode, you should define them outside of this block. + # + # If multitenancy management mode is not being used, routes can be included within + # this block and will still be accessible. +end diff --git a/config/routes/legislation.rb b/config/routes/legislation.rb index ae37f818b..37323a4f1 100644 --- a/config/routes/legislation.rb +++ b/config/routes/legislation.rb @@ -39,20 +39,3 @@ namespace :legislation do end end end - -resolve "Legislation::Proposal" do |proposal, options| - [proposal.process, :proposal, options.merge(id: proposal)] -end - -resolve "Vote" do |vote, options| - [*resource_hierarchy_for(vote.votable), vote, options] -end - -resolve "Legislation::Question" do |question, options| - [question.process, :question, options.merge(id: question)] -end - -resolve "Legislation::Annotation" do |annotation, options| - [annotation.draft_version.process, :draft_version, :annotation, - options.merge(draft_version_id: annotation.draft_version, id: annotation)] -end diff --git a/config/routes/poll.rb b/config/routes/poll.rb index f5be6b301..754c7c4d2 100644 --- a/config/routes/poll.rb +++ b/config/routes/poll.rb @@ -8,7 +8,3 @@ resources :polls, only: [:show, :index] do resources :answers, controller: "polls/answers", only: [:create, :destroy], shallow: false end end - -resolve "Poll::Question" do |question, options| - [:question, options.merge(id: question)] -end diff --git a/config/routes/sdg_management.rb b/config/routes/sdg_management.rb index d048bc2d1..fa496a63e 100644 --- a/config/routes/sdg_management.rb +++ b/config/routes/sdg_management.rb @@ -24,7 +24,3 @@ namespace :sdg_management do get "#{type}/:id/edit", to: "relations#edit", as: "edit_#{type.singularize}" end end - -resolve "SDG::LocalTarget" do |target, options| - [:local_target, options.merge(id: target)] -end diff --git a/config/secrets.yml.example b/config/secrets.yml.example index 4f298b089..fdc9285a6 100644 --- a/config/secrets.yml.example +++ b/config/secrets.yml.example @@ -22,6 +22,7 @@ development: authentication_logs: false devise_lockable: false multitenancy: false + multitenancy_management_mode: false security: # allowed_admin_ips: ["123.45.67.89", "192.168.1.0/24"] last_sign_in: false @@ -64,6 +65,7 @@ staging: managers_url: "" managers_application_key: "" multitenancy: false + multitenancy_management_mode: false security: # allowed_admin_ips: ["123.45.67.89", "192.168.1.0/24"] last_sign_in: false @@ -119,6 +121,7 @@ preproduction: managers_url: "" managers_application_key: "" multitenancy: false + multitenancy_management_mode: false security: # allowed_admin_ips: ["123.45.67.89", "192.168.1.0/24"] last_sign_in: false @@ -173,6 +176,7 @@ production: managers_url: "" managers_application_key: "" multitenancy: false + multitenancy_management_mode: false security: # allowed_admin_ips: ["123.45.67.89", "192.168.1.0/24"] last_sign_in: false diff --git a/docs/en/features/multitenancy.md b/docs/en/features/multitenancy.md index c9fea1ccb..2f54513d7 100644 --- a/docs/en/features/multitenancy.md +++ b/docs/en/features/multitenancy.md @@ -78,6 +78,21 @@ Note that, if you use a different domain for a tenant, you'll have to configure When adding a new tenant, an admin user **copying the same login data as the administrator creating the tenant** will be automatically created. Note this user is stored in the database schema of the new tenant, so changing their password in one tenant won't change their password in any other tenants. +### Multitenancy management mode + +The `multitenancy_management_mode` setting allows using the main tenant solely for managing other tenants and admin users, hiding any other admin panel functionality or public content. + +There are two possible ways to enable multitenancy management mode: + +* Adding `config.multitenancy_management_mode = true` inside the `class Application < Rails::Application` class in the `config/application_custom.rb` file +* Replacing the line `multitenancy_management_mode: false` with `multitenancy_management_mode: true` (or adding it if it isn't already there) in the `config/secrets.yml` file + +We recommend using the same method that has been used to enable the multitenancy functionality in the [Common step for all Consul Democracy installations](#common-step-for-all-consul-democracy-installations) section. + +After enabling this option, restart the application and you will see the administration panel as follows: + +![The administration panel only contains links to multitenancy and administrators](../../img/multitenancy/management-mode-en.png) + ## Steps to take after adding a tenant ### SSL certificates diff --git a/docs/es/features/multitenancy.md b/docs/es/features/multitenancy.md index ba259fa55..aabde1c11 100644 --- a/docs/es/features/multitenancy.md +++ b/docs/es/features/multitenancy.md @@ -78,6 +78,21 @@ Nótese que, si estás usando un dominio distinto para una entidad, tendrás que Al añadir una nueva entidad, se creará automáticamente un usuario con permiso de administrador para esta nueva entidad **cuyos datos de acceso serán una copia de los del administrador que crea la entidad**. Este usuario se almacenará en el esquema de base de datos de la nueva entidad, con lo que cambiar su contraseña en una entidad no cambiará su contraseña en otras entidades. +### Modo de gestión de multientidad + +La configuración `multitenancy_management_mode` permite utilizar la entidad principal únicamente para gestionar otras entidades y usuarios administradores, ocultando cualquier otra funcionalidad del panel de administración o contenido público. + +Existen dos posibles maneras de habilitar este modo de gestión de multientidad: + +* Añadiendo `config.multitenancy_management_mode = true` dentro de la clase `class Application < Rails::Application` del fichero `config/application_custom.rb` +* Cambiando la línea `multitenancy_management_mode: false` por `multitenancy_management_mode: true` (o añadiéndola si no está ya ahí) en el fichero `config/secrets.yml` + +Recomendamos utilizar el mismo método que se ha utilizado para habilitar la funcionalidad de multientidad en la sección [Paso común a todas las instalaciones de Consul Democracy](#paso-común-a-todas-las-instalaciones-de-consul-democracy). + +Tras habilitar esta opción, reinicia la aplicación y podrás ver el panel de administración de la siguiente manera: + +![El panel de administración sólo contiene enlaces a multientidad y administradores](../../img/multitenancy/management-mode-es.png) + ## Pasos a realizar tras añadir una entidad ### Certificados SSL diff --git a/docs/img/multitenancy/management-mode-en.png b/docs/img/multitenancy/management-mode-en.png new file mode 100644 index 000000000..300fda8ed Binary files /dev/null and b/docs/img/multitenancy/management-mode-en.png differ diff --git a/docs/img/multitenancy/management-mode-es.png b/docs/img/multitenancy/management-mode-es.png new file mode 100644 index 000000000..8a05db89e Binary files /dev/null and b/docs/img/multitenancy/management-mode-es.png differ diff --git a/spec/components/admin/menu_component_spec.rb b/spec/components/admin/menu_component_spec.rb index b05683237..74d13c421 100644 --- a/spec/components/admin/menu_component_spec.rb +++ b/spec/components/admin/menu_component_spec.rb @@ -1,6 +1,6 @@ require "rails_helper" -describe Admin::MenuComponent, controller: Admin::NewslettersController do +describe Admin::MenuComponent, :admin, controller: Admin::NewslettersController do it "disables all buttons when JavaScript isn't available" do render_inline Admin::MenuComponent.new @@ -20,6 +20,17 @@ describe Admin::MenuComponent, controller: Admin::NewslettersController do expect(page).to have_css "button[aria-expanded='false']", exact_text: "Settings" end + it "only renders the multitenancy and administrators sections in multitenancy management mode" do + allow(Rails.application.config).to receive(:multitenancy_management_mode).and_return(true) + + render_inline Admin::MenuComponent.new + + expect(page).to have_css "#admin_menu" + expect(page).to have_link "Multitenancy" + expect(page).to have_link "Administrators" + expect(page).to have_link count: 2 + end + describe "#polls_link" do it "is marked as current when managing poll options", controller: Admin::Poll::Questions::OptionsController do diff --git a/spec/components/layout/admin_header_component_spec.rb b/spec/components/layout/admin_header_component_spec.rb index 62a938369..61170b25e 100644 --- a/spec/components/layout/admin_header_component_spec.rb +++ b/spec/components/layout/admin_header_component_spec.rb @@ -35,4 +35,13 @@ describe Layout::AdminHeaderComponent do expect(page).not_to have_css "[data-toggle]" end end + + it "does not show link to root path when multitenancy_management_mode is enabled" do + allow(Rails.application.config).to receive(:multitenancy_management_mode).and_return(true) + create(:administrator, user: user) + + render_inline Layout::AdminHeaderComponent.new(user) + + expect(page).not_to have_link "Go back to CONSUL" + end end diff --git a/spec/components/layout/admin_login_items_component_spec.rb b/spec/components/layout/admin_login_items_component_spec.rb index 8d1316ec1..964f1df06 100644 --- a/spec/components/layout/admin_login_items_component_spec.rb +++ b/spec/components/layout/admin_login_items_component_spec.rb @@ -15,6 +15,15 @@ describe Layout::AdminLoginItemsComponent do expect(page).not_to be_rendered end + it "is not rendered when multitenancy_management_mode is enabled" do + allow(Rails.application.config).to receive(:multitenancy_management_mode).and_return(true) + user = create(:administrator).user + + render_inline Layout::AdminLoginItemsComponent.new(user) + + expect(page).not_to be_rendered + end + it "shows access to all places except officing to administrators" do user = create(:administrator).user diff --git a/spec/components/layout/footer_component_spec.rb b/spec/components/layout/footer_component_spec.rb index 59cb2cf73..9a078fc23 100644 --- a/spec/components/layout/footer_component_spec.rb +++ b/spec/components/layout/footer_component_spec.rb @@ -13,4 +13,11 @@ describe Layout::FooterComponent do end end end + + it "is not rendered when multitenancy_management_mode is enabled" do + allow(Rails.application.config).to receive(:multitenancy_management_mode).and_return(true) + render_inline Layout::FooterComponent.new + + expect(page).not_to be_rendered + end end diff --git a/spec/components/layout/login_items_component_spec.rb b/spec/components/layout/login_items_component_spec.rb new file mode 100644 index 000000000..eecadd2b2 --- /dev/null +++ b/spec/components/layout/login_items_component_spec.rb @@ -0,0 +1,11 @@ +require "rails_helper" + +describe Layout::LoginItemsComponent do + it "does not show the my activity link when multitenancy_management_mode is enabled" do + allow(Rails.application.config).to receive(:multitenancy_management_mode).and_return(true) + + render_inline Layout::LoginItemsComponent.new(create(:user)) + + expect(page).not_to have_content "My content" + end +end diff --git a/spec/components/layout/notification_item_component_spec.rb b/spec/components/layout/notification_item_component_spec.rb new file mode 100644 index 000000000..7b7777829 --- /dev/null +++ b/spec/components/layout/notification_item_component_spec.rb @@ -0,0 +1,22 @@ +require "rails_helper" + +describe Layout::NotificationItemComponent do + it "is not rendered for anonymous users" do + render_inline Layout::NotificationItemComponent.new(nil) + + expect(page).not_to be_rendered + end + + it "is rendered for identified users" do + render_inline Layout::NotificationItemComponent.new(create(:user)) + + expect(page).to be_rendered + end + + it "is not rendered when multitenancy_management_mode is enabled" do + allow(Rails.application.config).to receive(:multitenancy_management_mode).and_return(true) + render_inline Layout::NotificationItemComponent.new(create(:user)) + + expect(page).not_to be_rendered + end +end diff --git a/spec/components/layout/subnavigation_component_spec.rb b/spec/components/layout/subnavigation_component_spec.rb new file mode 100644 index 000000000..bccca4c05 --- /dev/null +++ b/spec/components/layout/subnavigation_component_spec.rb @@ -0,0 +1,10 @@ +require "rails_helper" + +describe Layout::SubnavigationComponent do + it "is not rendered when multitenancy_management_mode is enabled" do + allow(Rails.application.config).to receive(:multitenancy_management_mode).and_return(true) + render_inline Layout::SubnavigationComponent.new + + expect(page).not_to be_rendered + end +end diff --git a/spec/controllers/users/sessions_controller_spec.rb b/spec/controllers/users/sessions_controller_spec.rb index 5f13ddb10..dc666a5ff 100644 --- a/spec/controllers/users/sessions_controller_spec.rb +++ b/spec/controllers/users/sessions_controller_spec.rb @@ -69,4 +69,22 @@ describe Users::SessionsController do end end end + + describe "after_sign_in_path_for" do + it "redirects to account path when multitenancy_management_mode is enabled and user is not an admin" do + allow(Rails.application.config).to receive(:multitenancy_management_mode).and_return(true) + + post :create, params: { user: { login: "citizen@consul.org", password: "12345678" }} + + expect(response).to redirect_to account_path + end + + it "redirects to welcome path when multitenancy_management_mode is disabled" do + allow(Rails.application.config).to receive(:multitenancy_management_mode).and_return(false) + + post :create, params: { user: { login: "citizen@consul.org", password: "12345678" }} + + expect(response).to redirect_to welcome_path + end + end end diff --git a/spec/system/multitenancy_management_mode_spec.rb b/spec/system/multitenancy_management_mode_spec.rb new file mode 100644 index 000000000..943ed8331 --- /dev/null +++ b/spec/system/multitenancy_management_mode_spec.rb @@ -0,0 +1,58 @@ +require "rails_helper" + +describe "Multitenancy management mode", :admin do + before do + allow(Rails.application.config).to receive(:multitenancy_management_mode).and_return(true) + Setting["org_name"] = "CONSUL" + end + + scenario "renders expected content for multitenancy manage mode in admin section" do + visit admin_root_path + + within ".top-links" do + expect(page).not_to have_content "Go back to CONSUL" + end + + within ".top-bar" do + expect(page).to have_css "li", count: 2 + expect(page).to have_content "My account" + expect(page).to have_content "Sign out" + end + + within "#admin_menu" do + expect(page).to have_content "Multitenancy" + expect(page).to have_content "Administrators" + expect(page).to have_css "li", count: 2 + end + end + + scenario "redirects root path requests to the admin tenants path" do + visit root_path + + expect(page).to have_content "CONSUL ADMINISTRATION", normalize_ws: true + expect(page).to have_content "Multitenancy" + expect(page).not_to have_content "Most active proposals" + end + + scenario "does not redirect other tenants when visiting the root path", :seed_tenants do + create(:tenant, schema: "mars") + + with_subdomain("mars") do + visit root_path + + expect(page).to have_content "Most active proposals" + expect(page).not_to have_content "Multitenancy" + expect(page).not_to have_content "CONSUL ADMINISTRATION", normalize_ws: true + end + end + + scenario "redirects to account path when regular users try to access the admin section" do + logout + login_as(create(:user)) + + visit admin_root_path + + expect(page).to have_current_path account_path + expect(page).to have_content "You do not have permission to access this page." + end +end