FEAT: init admins group

This commit is contained in:
seb-by-ouidou 2023-08-25 20:53:30 +00:00 committed by seb-by-ouidou
parent b7d6e9e30f
commit b100c8380e
35 changed files with 604 additions and 0 deletions

View file

@ -0,0 +1,9 @@
module AdminsGroupManagers
class AdminsGroupManagerController < ApplicationController
before_action :authenticate_admins_group_manager!
def nav_bar_profile
:admins_group_manager
end
end
end

View file

@ -0,0 +1,14 @@
module AdminsGroupManagers
class AdminsGroupsController < AdminsGroupManagerController
def index
@admins_groups = admins_groups
end
private
def admins_groups
admins_group_ids = current_admins_group_manager.admins_group_ids
AdminsGroup.where(id: admins_group_ids.compact.uniq)
end
end
end

View file

@ -0,0 +1,4 @@
module Manager
class AdminsGroupManagersController < Manager::ApplicationController
end
end

View file

@ -0,0 +1,32 @@
module Manager
class AdminsGroupsController < Manager::ApplicationController
def add_admins_group_manager
emails = (params['emails'].presence || '').split(',').to_json
emails = JSON.parse(emails).map { EmailSanitizableConcern::EmailSanitizer.sanitize(_1) }
admins_group_managers, invalid_emails = admins_group.add_admins_group_managers(emails:)
if invalid_emails.present?
flash[:alert] = t('.wrong_address',
count: invalid_emails.size,
emails: invalid_emails)
end
if admins_group_managers.present?
flash[:notice] = "Les gestionnaires ont bien été affectés au groupe d'administrateurs"
AdminsGroupMailer
.notify_added_admins_group_managers(admins_group, admins_group_managers, current_super_admin.email)
.deliver_later
end
redirect_to manager_admins_groups_path(admins_group)
end
private
def admins_group
@admins_group ||= AdminsGroup.find(params[:id])
end
end
end

View file

@ -0,0 +1,54 @@
require "administrate/base_dashboard"
class AdminsGroupDashboard < Administrate::BaseDashboard
# ATTRIBUTE_TYPES
# a hash that describes the type of each of the model's fields.
#
# Each different type represents an Administrate::Field object,
# which determines how the attribute is displayed
# on pages throughout the dashboard.
ATTRIBUTE_TYPES = {
id: Field::Number,
name: Field::String,
created_at: Field::DateTime,
updated_at: Field::DateTime,
admins_group_managers: Field::HasMany,
administrateurs: Field::HasMany
}.freeze
# COLLECTION_ATTRIBUTES
# an array of attributes that will be displayed on the model's index page.
#
# By default, it's limited to four items to reduce clutter on index pages.
# Feel free to add, remove, or rearrange items.
COLLECTION_ATTRIBUTES = [
:id,
:created_at,
:name,
:admins_group_managers,
:administrateurs
].freeze
# SHOW_PAGE_ATTRIBUTES
# an array of attributes that will be displayed on the model's show page.
SHOW_PAGE_ATTRIBUTES = [
:admins_group_managers,
:administrateurs,
:id,
:created_at
].freeze
# FORM_ATTRIBUTES
# an array of attributes that will be displayed
# on the model's form (`new` and `edit`) pages.
FORM_ATTRIBUTES = [
:name
].freeze
# Overwrite this method to customize how users are displayed
# across all pages of the admin dashboard.
#
def display_resource(admins_group)
admins_group.name
end
end

View file

@ -0,0 +1,57 @@
require "administrate/base_dashboard"
class AdminsGroupManagerDashboard < Administrate::BaseDashboard
# ATTRIBUTE_TYPES
# a hash that describes the type of each of the model's fields.
#
# Each different type represents an Administrate::Field object,
# which determines how the attribute is displayed
# on pages throughout the dashboard.
ATTRIBUTE_TYPES = {
id: Field::Number,
user: Field::HasOne.with_options(searchable: true, searchable_field: 'email'),
created_at: Field::DateTime,
updated_at: Field::DateTime,
admins_groups: Field::HasMany.with_options(limit: 20),
registration_state: Field::String.with_options(searchable: false),
email: Field::Email.with_options(searchable: false)
}.freeze
# COLLECTION_ATTRIBUTES
# an array of attributes that will be displayed on the model's index page.
#
# By default, it's limited to four items to reduce clutter on index pages.
# Feel free to add, remove, or rearrange items.
COLLECTION_ATTRIBUTES = [
:id,
:user,
:created_at,
:admins_groups,
:registration_state
].freeze
# SHOW_PAGE_ATTRIBUTES
# an array of attributes that will be displayed on the model's show page.
SHOW_PAGE_ATTRIBUTES = [
:id,
:user,
:created_at,
:updated_at,
:registration_state,
:admins_groups
].freeze
# FORM_ATTRIBUTES
# an array of attributes that will be displayed
# on the model's form (`new` and `edit`) pages.
FORM_ATTRIBUTES = [
:email
].freeze
# Overwrite this method to customize how users are displayed
# across all pages of the admin dashboard.
#
def display_resource(admins_group_manager)
admins_group_manager.email
end
end

View file

@ -0,0 +1,13 @@
class AdminsGroupMailer < ApplicationMailer
layout 'mailers/layout'
def notify_added_admins_group_managers(admins_group, added_admins_group_managers, current_super_admin_email)
added_admins_group_manager_emails = added_admins_group_managers.map(&:email)
@admins_group = admins_group
@current_super_admin_email = current_super_admin_email
subject = "Vous avez été ajouté(e) en tant que gestionnaire du groupe d'administrateur \"#{admins_group.name}\""
mail(bcc: added_admins_group_manager_emails, subject: subject)
end
end

View file

@ -0,0 +1,30 @@
class AdminsGroup < ApplicationRecord
belongs_to :admins_group, optional: true # parent
has_many :children, class_name: "AdminsGroup", inverse_of: :admins_group
has_many :administrateurs
has_and_belongs_to_many :admins_group_managers
def add(admins_group_manager)
admins_group_managers << admins_group_manager
end
def add_admins_group_managers(ids: [], emails: [])
admins_group_managers_to_add, valid_emails, invalid_emails = AdminsGroupManager.find_all_by_identifier_with_emails(ids:, emails:)
not_found_emails = valid_emails - admins_group_managers_to_add.map(&:email)
# Send invitations to users without account
if not_found_emails.present?
admins_group_managers_to_add += not_found_emails.map do |email|
user = User.create_or_promote_to_admins_group_manager(email, SecureRandom.hex)
user.invite_admins_group_manager!(self)
user.admins_group_manager
end
end
# We dont't want to assign a user to an admins_group if they are already assigned to it
admins_group_managers_to_add -= admins_group_managers
admins_group_managers_to_add.each { add(_1) }
[admins_group_managers_to_add, invalid_emails]
end
end

View file

@ -0,0 +1,41 @@
class AdminsGroupManager < ApplicationRecord
has_and_belongs_to_many :admins_groups
belongs_to :user
delegate :email, to: :user
default_scope { eager_load(:user) }
def self.by_email(email)
find_by(users: { email: email })
end
def self.find_all_by_identifier(ids: [], emails: [])
find_all_by_identifier_with_emails(ids:, emails:).first
end
def self.find_all_by_identifier_with_emails(ids: [], emails: [])
valid_emails, invalid_emails = emails.partition { URI::MailTo::EMAIL_REGEXP.match?(_1) }
[
where(id: ids).or(where(users: { email: valid_emails })).distinct(:id),
valid_emails,
invalid_emails
]
end
def can_be_deleted?
!(root_admins_group = admins_groups.where(admins_group: nil).first) || root_admins_group.admins_group_managers.size > 1
end
def registration_state
if user.active?
'Actif'
elsif user.reset_password_period_valid?
'En attente'
else
'Expiré'
end
end
end

View file

@ -0,0 +1,6 @@
%p= t(:hello, scope: [:views, :shared, :greetings])
%p
= t(".email_body", admins_group_name: @admins_group.name, email: @current_super_admin_email, application_name: APPLICATION_NAME)
= render partial: "layouts/mailers/signature"

View file

@ -0,0 +1,16 @@
#breadcrumbs.sub-header
.fr-container.flex.justify-between.align-baseline.column
%nav.fr-breadcrumb.mt-0{ role: "navigation", aria: { label: t('you_are_here', scope: [:layouts, :breadcrumb]) } }
%button.fr-breadcrumb__button{ aria: { expanded: "false", controls: "breadcrumb-1" } }
= t('show', scope: [:layouts, :breadcrumb])
.fr-collapse#breadcrumb-1
%ol.fr-breadcrumb__list
%li= link_to t('root', scope: [:layouts, :breadcrumb]), root_path, class: 'fr-breadcrumb__link'
- steps.each.with_index do |step, i|
- if i == steps.size - 1
%li{ aria: { current: "page" } }
%span.fr-breadcrumb__link= step[0]
- else
%li= link_to step[0], step[1], class: 'fr-breadcrumb__link'

View file

@ -0,0 +1,17 @@
= render partial: 'admins_group_managers/breadcrumbs',
locals: { steps: [['Groupes d\'administrateurs', admins_group_manager_admins_groups_path]] }
#admins_groups-index.container
%h1.fr-h1 Liste des groupes d'administrateurs
%table.fr-table.width-100.mt-3
%thead
%tr
%th{ scope: "col" }
Nom
%tbody
- @admins_groups.each do |admins_group|
%tr
%td
= admins_group.name

View file

@ -0,0 +1,57 @@
<%#
# Show
This view is the template for the show page.
It renders the attributes of a resource,
as well as a link to its edit page.
## Local variables:
- `page`:
An instance of [Administrate::Page::Show][1].
Contains methods for accessing the resource to be displayed on the page,
as well as helpers for describing how each attribute of the resource
should be displayed.
[1]: http://www.rubydoc.info/gems/administrate/Administrate/Page/Show
%>
<% content_for(:title) { t("administrate.actions.show_resource", name: page.page_title) } %>
<% admins_group = page.resource %>
<header class="main-content__header" role="banner">
<h1 class="main-content__page-title">
<%= content_for(:title) %>
</h1>
<div>
<%= link_to(
t("administrate.actions.edit_resource", name: page.page_title),
[:edit, namespace, page.resource],
class: "button",
) if valid_action? :edit %>
</div>
</header>
<section class="main-content__body">
<dl>
<% page.attributes.each do |attribute| %>
<dt class="attribute-label" id="<%= attribute.name %>">
<%= t(
"helpers.label.#{resource_name}.#{attribute.name}",
default: attribute.name.titleize,
) %>
</dt>
<dd class="attribute-data attribute-data--<%=attribute.html_class%>">
<%= render_field attribute, page: page %>
<% if attribute.name == 'admins_group_managers' %>
<%= form_tag(add_admins_group_manager_manager_admins_group_path(admins_group), style: 'margin-top: 1rem;') do %>
<%= email_field_tag(:emails, '', placeholder: 'Emails', autocapitalize: 'off', autocorrect: 'off', spellcheck: 'false', style: 'margin-bottom: 1rem;width:24rem;') %>
<button>Ajouter un gestionnaire</button>
<% end %>
<% end %>
</dd>
<% end %>
</dl>
</section>

View file

@ -0,0 +1,13 @@
- content_for(:title, 'Activation de votre compte gestionnaire')
%p
Bonjour,
%p
Vous venez dêtre nommé gestionnaire du groupe #{@admins_group.name} sur #{APPLICATION_NAME}.
%p
Votre compte a été créé pour l'adresse email #{@user.email}. Pour lactiver, nous vous invitons à cliquer sur le lien suivant : 
= link_to(users_activate_url(token: @reset_password_token), users_activate_url(token: @reset_password_token))
= render partial: "layouts/mailers/signature"

View file

@ -155,3 +155,6 @@ CLAMAV_ENABLED="disabled"
# Siret number used for API Entreprise, by default we use SIRET from dinum
API_ENTREPRISE_DEFAULT_SIRET="put_your_own_siret"
# Admins group usage (gestionnaire de groupes d'administrateurs)
ADMINS_GROUP_ENABLED="disabled"

View file

@ -0,0 +1,9 @@
fr:
activerecord:
attributes:
admins_group:
admins_group_managers: Gestionnaires
models:
admins_group:
one: Groupe d'administrateurs
other: Groupes d'administrateurs

View file

@ -0,0 +1,9 @@
fr:
activerecord:
attributes:
admins_group_manager:
admins_groups: Groupes
models:
admins_group_manager:
one: Gestionnaire
other: Gestionnaires

View file

@ -0,0 +1,4 @@
en:
admins_group_mailer:
notify_added_admins_group_managers:
email_body: "You were assigned as manager on the admins group %{admins_group_name} on %{application_name} by « %{email} »"

View file

@ -0,0 +1,4 @@
fr:
admins_group_mailer:
notify_added_admins_group_managers:
email_body: "Vous venez dêtre nommé gestionnaire du groupe %{admins_group_name} sur %{application_name} par « %{email} »."

View file

@ -0,0 +1,9 @@
class CreateAdminsGroupManagers < ActiveRecord::Migration[7.0]
def change
create_table "admins_group_managers" do |t|
t.bigint :user_id, null: false
t.index [:user_id], name: :index_admins_group_managers_on_user_id
t.timestamps
end
end
end

View file

@ -0,0 +1,15 @@
class CreateAdminsGroups < ActiveRecord::Migration[7.0]
def change
create_table "admins_groups" do |t|
t.string :name, null: false
t.references :admins_group
t.index [:name], name: :index_admins_groups_on_name
t.timestamps
end
create_join_table :admins_groups, :admins_group_managers do |t|
t.index [:admins_group_id, :admins_group_manager_id], name: :index_on_admins_group_and_admins_group_manager
t.index [:admins_group_manager_id, :admins_group_id], name: :index_on_admins_group_manager_and_admins_group
end
end
end

View file

@ -0,0 +1,7 @@
class AddAdminsGroupToAdministrateurs < ActiveRecord::Migration[7.0]
disable_ddl_transaction!
def change
add_reference :administrateurs, :admins_group, index: { algorithm: :concurrently }, null: true
end
end

View file

@ -0,0 +1,5 @@
class AddForeignKeyAdminsGroupToAdministrateurs < ActiveRecord::Migration[7.0]
def change
add_foreign_key :administrateurs, :admins_groups, validate: false
end
end

View file

@ -0,0 +1,5 @@
class ValidateForeignKeyAdminsGroupToAdministrateurs < ActiveRecord::Migration[7.0]
def change
validate_foreign_key :administrateurs, :admins_groups
end
end

View file

@ -61,6 +61,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_10_083144) do
end
create_table "administrateurs", id: :serial, force: :cascade do |t|
t.bigint "admins_group_id"
t.datetime "created_at", precision: 6
t.bigint "groupe_gestionnaire_id"
t.datetime "updated_at", precision: 6
@ -90,6 +91,29 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_10_083144) do
t.index ["procedure_id"], name: "index_administrateurs_procedures_on_procedure_id"
end
create_table "admins_group_managers", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "user_id", null: false
t.index ["user_id"], name: "index_admins_group_managers_on_user_id"
end
create_table "admins_group_managers_groups", id: false, force: :cascade do |t|
t.bigint "admins_group_id", null: false
t.bigint "admins_group_manager_id", null: false
t.index ["admins_group_id", "admins_group_manager_id"], name: "index_on_admins_group_and_admins_group_manager"
t.index ["admins_group_manager_id", "admins_group_id"], name: "index_on_admins_group_manager_and_admins_group"
end
create_table "admins_groups", force: :cascade do |t|
t.bigint "admins_group_id"
t.datetime "created_at", null: false
t.string "name", null: false
t.datetime "updated_at", null: false
t.index ["admins_group_id"], name: "index_admins_groups_on_admins_group_id"
t.index ["name"], name: "index_admins_groups_on_name"
end
create_table "api_tokens", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.bigint "administrateur_id", null: false
t.bigint "allowed_procedure_ids", array: true

View file

@ -0,0 +1,12 @@
describe AdminsGroupManagers::AdminsGroupManagerController, type: :controller do
describe 'before actions: authenticate_admins_group_manager!' do
it 'is present' do
before_actions = AdminsGroupManagers::AdminsGroupManagerController
._process_action_callbacks
.filter { |process_action_callbacks| process_action_callbacks.kind == :before }
.map(&:filter)
expect(before_actions).to include(:authenticate_admins_group_manager!)
end
end
end

View file

@ -0,0 +1,23 @@
describe AdminsGroupManagers::AdminsGroupsController, type: :controller do
let(:admins_group_manager) { create(:admins_group_manager) }
describe "#index" do
subject { get :index }
context "when not logged" do
before { subject }
it { expect(response).to redirect_to(new_user_session_path) }
end
context "when logged in" do
let!(:admins_group) { create(:admins_group, admins_group_managers: [admins_group_manager]) }
before do
sign_in(admins_group_manager.user)
subject
end
it { expect(response).to have_http_status(:ok) }
it { expect(assigns(:admins_groups)).to include(admins_group) }
end
end
end

View file

@ -0,0 +1,25 @@
describe Manager::AdminsGroupManagersController, type: :controller do
let(:super_admin) { create(:super_admin) }
let(:admins_group_manager) { create(:admins_group_manager) }
before { sign_in super_admin }
describe '#index' do
render_views
it 'searches admin by email' do
get :index, params: { search: admins_group_manager.email }
expect(response).to have_http_status(:success)
end
end
describe '#show' do
render_views
before do
get :show, params: { id: admins_group_manager.id }
end
it { expect(response.body).to include(admins_group_manager.email) }
end
end

View file

@ -0,0 +1,27 @@
describe Manager::AdminsGroupsController, type: :controller do
let(:super_admin) { create(:super_admin) }
let(:admins_group) { create(:admins_group) }
before { sign_in super_admin }
describe '#index' do
render_views
before do
admins_group
get :index
end
it { expect(response.body).to include(admins_group.name) }
end
describe '#show' do
render_views
before do
get :show, params: { id: admins_group.id }
end
it { expect(response.body).to include(admins_group.name) }
end
end

View file

@ -0,0 +1,5 @@
FactoryBot.define do
factory :admins_group do
sequence(:name) { |n| "Group #{n}" }
end
end

View file

@ -0,0 +1,12 @@
FactoryBot.define do
sequence(:admins_group_manager_email) { |n| "admins_group_manager#{n}@demarches-simplifiees.fr" }
factory :admins_group_manager do
user { association :user, email: email, password: password }
transient do
email { generate(:admins_group_manager_email) }
password { 'somethingverycomplated!' }
end
end
end

View file

@ -0,0 +1,16 @@
RSpec.describe AdminsGroupMailer, type: :mailer do
describe '#notify_added_admins_group_managers' do
let(:admins_group) { create(:admins_group) }
let(:admins_group_managers_to_add) { [create(:admins_group_manager, email: 'int3@g'), create(:admins_group_manager, email: 'int4@g')] }
let(:current_super_admin_email) { 'toto@email.com' }
subject { described_class.notify_added_admins_group_managers(admins_group, admins_group_managers_to_add, current_super_admin_email) }
before { admins_group_managers_to_add.each { admins_group.add(_1) } }
it { expect(subject.body).to include('Vous venez dêtre nommé gestionnaire du groupe') }
it { expect(subject.bcc).to match_array(['int3@g', 'int4@g']) }
end
end

View file

@ -0,0 +1,14 @@
class AdminsGroupMailerPreview < ActionMailer::Preview
def notify_added_admins_group_managers
admins_group = AdminsGroup.new(name: 'un groupe d\'admin')
current_super_admin_email = 'admin@dgfip.com'
admins_group_managers = [AdminsGroupManager.new(user: user)]
AdminsGroupMailer.notify_added_admins_group_managers(admins_group, admins_group_managers, current_super_admin_email)
end
private
def user
User.new(id: 10, email: 'test@exemple.fr')
end
end

View file

@ -0,0 +1,5 @@
describe AdminsGroupManager, type: :model do
describe 'associations' do
it { is_expected.to have_and_belong_to_many(:admins_groups) }
end
end

View file

@ -0,0 +1,8 @@
describe AdminsGroup, type: :model do
describe 'associations' do
it { is_expected.to belong_to(:admins_group).optional }
it { is_expected.to have_many(:children) }
it { is_expected.to have_many(:administrateurs) }
it { is_expected.to have_and_belong_to_many(:admins_group_managers) }
end
end