diff --git a/app/assets/stylesheets/groupe_gestionnaire_cards.scss b/app/assets/stylesheets/groupe_gestionnaire_cards.scss new file mode 100644 index 000000000..4ddeefdf3 --- /dev/null +++ b/app/assets/stylesheets/groupe_gestionnaire_cards.scss @@ -0,0 +1,12 @@ +.fr-groupe_gestionnaire_cards { + .fr-h6 { + padding-left: 20px; + padding-right: 20px; + position: relative; +} + + .notifications { + top: 3px; + right: 3px; + } +} diff --git a/app/components/groupe_gestionnaire/card/administrateurs_component.rb b/app/components/groupe_gestionnaire/card/administrateurs_component.rb index 3551f5732..231cdd593 100644 --- a/app/components/groupe_gestionnaire/card/administrateurs_component.rb +++ b/app/components/groupe_gestionnaire/card/administrateurs_component.rb @@ -1,6 +1,7 @@ class GroupeGestionnaire::Card::AdministrateursComponent < ApplicationComponent - def initialize(groupe_gestionnaire:, path:) + def initialize(groupe_gestionnaire:, path:, is_gestionnaire: true) @groupe_gestionnaire = groupe_gestionnaire @path = path + @is_gestionnaire = is_gestionnaire end end diff --git a/app/components/groupe_gestionnaire/card/administrateurs_component/administrateurs_component.html.haml b/app/components/groupe_gestionnaire/card/administrateurs_component/administrateurs_component.html.haml index de7014288..0cdb0d3e9 100644 --- a/app/components/groupe_gestionnaire/card/administrateurs_component/administrateurs_component.html.haml +++ b/app/components/groupe_gestionnaire/card/administrateurs_component/administrateurs_component.html.haml @@ -7,4 +7,4 @@ %p.fr-tag= @groupe_gestionnaire.administrateurs.size %h3.fr-h6 = t('.title', count: @groupe_gestionnaire.administrateurs.size) - %p.fr-btn.fr-btn--tertiary= t('views.shared.actions.edit') + %p.fr-btn.fr-btn--tertiary= @is_gestionnaire ? t('views.shared.actions.edit') : t('views.shared.actions.see') diff --git a/app/components/groupe_gestionnaire/card/commentaires_component.rb b/app/components/groupe_gestionnaire/card/commentaires_component.rb index e97990032..3ec44b459 100644 --- a/app/components/groupe_gestionnaire/card/commentaires_component.rb +++ b/app/components/groupe_gestionnaire/card/commentaires_component.rb @@ -1,8 +1,9 @@ class GroupeGestionnaire::Card::CommentairesComponent < ApplicationComponent - def initialize(groupe_gestionnaire:, administrateur:, path:) + def initialize(groupe_gestionnaire:, administrateur:, path:, unread_commentaires: nil) @groupe_gestionnaire = groupe_gestionnaire @administrateur = administrateur @path = path + @unread_commentaires = unread_commentaires end def number_commentaires diff --git a/app/components/groupe_gestionnaire/card/commentaires_component/commentaires_component.html.haml b/app/components/groupe_gestionnaire/card/commentaires_component/commentaires_component.html.haml index 984401841..159d3b475 100644 --- a/app/components/groupe_gestionnaire/card/commentaires_component/commentaires_component.html.haml +++ b/app/components/groupe_gestionnaire/card/commentaires_component/commentaires_component.html.haml @@ -1,10 +1,12 @@ .fr-col-6.fr-col-md-4.fr-col-lg-3 - = link_to @path, id: 'administrateurs', class: 'fr-tile fr-enlarge-link' do + = link_to @path, id: 'commentaires', class: 'fr-tile fr-enlarge-link' do .fr-tile__body.flex.column.align-center.justify-between %p.fr-badge.fr-badge--success Validé %div .line-count.fr-my-1w %p.fr-tag= number_commentaires %h3.fr-h6 + - if @unread_commentaires + %span.notifications{ 'aria-label': 'notifications' } = t('.title', count: number_commentaires) %p.fr-btn.fr-btn--tertiary= t('views.shared.actions.see') diff --git a/app/components/groupe_gestionnaire/card/gestionnaires_component.rb b/app/components/groupe_gestionnaire/card/gestionnaires_component.rb index ef51f10bd..43b0c40a7 100644 --- a/app/components/groupe_gestionnaire/card/gestionnaires_component.rb +++ b/app/components/groupe_gestionnaire/card/gestionnaires_component.rb @@ -1,6 +1,7 @@ class GroupeGestionnaire::Card::GestionnairesComponent < ApplicationComponent - def initialize(groupe_gestionnaire:, path:) + def initialize(groupe_gestionnaire:, path:, is_gestionnaire: true) @groupe_gestionnaire = groupe_gestionnaire @path = path + @is_gestionnaire = is_gestionnaire end end diff --git a/app/components/groupe_gestionnaire/card/gestionnaires_component/gestionnaires_component.html.haml b/app/components/groupe_gestionnaire/card/gestionnaires_component/gestionnaires_component.html.haml index 6193a89be..cbcedb3fd 100644 --- a/app/components/groupe_gestionnaire/card/gestionnaires_component/gestionnaires_component.html.haml +++ b/app/components/groupe_gestionnaire/card/gestionnaires_component/gestionnaires_component.html.haml @@ -7,4 +7,4 @@ %p.fr-tag= @groupe_gestionnaire.gestionnaires.size %h3.fr-h6 = t('.title', count: @groupe_gestionnaire.gestionnaires.size) - %p.fr-btn.fr-btn--tertiary= t('views.shared.actions.edit') + %p.fr-btn.fr-btn--tertiary= @is_gestionnaire ? t('views.shared.actions.edit') : t('views.shared.actions.see') diff --git a/app/components/groupe_gestionnaire/groupe_gestionnaire_commentaires/commentaire_component.rb b/app/components/groupe_gestionnaire/groupe_gestionnaire_commentaires/commentaire_component.rb index 98e87b747..9729a8adc 100644 --- a/app/components/groupe_gestionnaire/groupe_gestionnaire_commentaires/commentaire_component.rb +++ b/app/components/groupe_gestionnaire/groupe_gestionnaire_commentaires/commentaire_component.rb @@ -1,15 +1,28 @@ class GroupeGestionnaire::GroupeGestionnaireCommentaires::CommentaireComponent < ApplicationComponent include ApplicationHelper - def initialize(commentaire:, connected_user:, is_gestionnaire: true) + def initialize(commentaire:, connected_user:, commentaire_seen_at: nil, is_gestionnaire: true) @commentaire = commentaire @connected_user = connected_user @is_gestionnaire = is_gestionnaire @groupe_gestionnaire = commentaire.groupe_gestionnaire + @commentaire_seen_at = commentaire_seen_at end private + def highlight_if_unseen_class + if highlight? + 'highlighted' + end + end + + def scroll_to_target + if highlight? + { scroll_to_target: 'to' } + end + end + def commentaire_issuer if @commentaire.sent_by?(@connected_user) t('.you') @@ -22,4 +35,8 @@ class GroupeGestionnaire::GroupeGestionnaireCommentaires::CommentaireComponent < is_current_year = (@commentaire.created_at.year == Time.zone.today.year) l(@commentaire.created_at, format: is_current_year ? :message_date : :message_date_with_year) end + + def highlight? + @commentaire.persisted? && (@commentaire_seen_at.nil? || @commentaire_seen_at < @commentaire.created_at) + end end diff --git a/app/components/groupe_gestionnaire/groupe_gestionnaire_commentaires/commentaire_component/commentaire_component.html.haml b/app/components/groupe_gestionnaire/groupe_gestionnaire_commentaires/commentaire_component/commentaire_component.html.haml index eea959a25..537f2b0a1 100644 --- a/app/components/groupe_gestionnaire/groupe_gestionnaire_commentaires/commentaire_component/commentaire_component.html.haml +++ b/app/components/groupe_gestionnaire/groupe_gestionnaire_commentaires/commentaire_component/commentaire_component.html.haml @@ -3,7 +3,7 @@ %span.mail = commentaire_issuer - %span.date{ class: ["fr-text--xs", "fr-text-mention--grey", "font-weight-normal"] } + %span.date{ class: ["fr-text--xs", "fr-text-mention--grey", "font-weight-normal", highlight_if_unseen_class], data: scroll_to_target } = commentaire_date .rich-text - if @commentaire.discarded? diff --git a/app/components/groupe_gestionnaire/groupe_gestionnaire_list_commentaires/commentaire_component.rb b/app/components/groupe_gestionnaire/groupe_gestionnaire_list_commentaires/commentaire_component.rb index 91540ff2c..2ea54f48c 100644 --- a/app/components/groupe_gestionnaire/groupe_gestionnaire_list_commentaires/commentaire_component.rb +++ b/app/components/groupe_gestionnaire/groupe_gestionnaire_list_commentaires/commentaire_component.rb @@ -23,4 +23,9 @@ class GroupeGestionnaire::GroupeGestionnaireListCommentaires::CommentaireCompone gestionnaire_groupe_gestionnaire_commentaire_path(@groupe_gestionnaire, @commentaire), class: 'button' end + + def highlight? + commentaire_seen_at = current_gestionnaire.commentaire_seen_at(@groupe_gestionnaire, @commentaire.sender) + commentaire_seen_at.nil? || commentaire_seen_at < @commentaire.created_at + end end diff --git a/app/components/groupe_gestionnaire/groupe_gestionnaire_list_commentaires/commentaire_component/commentaire_component.html.haml b/app/components/groupe_gestionnaire/groupe_gestionnaire_list_commentaires/commentaire_component/commentaire_component.html.haml index 82114d035..e776d1b85 100644 --- a/app/components/groupe_gestionnaire/groupe_gestionnaire_list_commentaires/commentaire_component/commentaire_component.html.haml +++ b/app/components/groupe_gestionnaire/groupe_gestionnaire_list_commentaires/commentaire_component/commentaire_component.html.haml @@ -1,4 +1,7 @@ %tr{ id: dom_id(@commentaire) } - %td= email + %td + = email + - if highlight? + %span.notifications{ 'aria-label': 'notifications' } %td= created_at %td= see_button diff --git a/app/controllers/administrateurs/activate_controller.rb b/app/controllers/administrateurs/activate_controller.rb index cb9394292..6304ddf7e 100644 --- a/app/controllers/administrateurs/activate_controller.rb +++ b/app/controllers/administrateurs/activate_controller.rb @@ -25,13 +25,13 @@ class Administrateurs::ActivateController < ApplicationController reset_password_token: update_administrateur_params[:reset_password_token] }) - if user&.administrateur&.errors&.empty? + if user&.errors&.empty? sign_in(user, scope: :user) flash.notice = "Mot de passe enregistré" redirect_to admin_procedures_path else - flash.alert = user.administrateur.errors.full_messages + flash.alert = user.errors.full_messages redirect_to admin_activate_path(token: update_administrateur_params[:reset_password_token]) end end diff --git a/app/controllers/gestionnaires/activate_controller.rb b/app/controllers/gestionnaires/activate_controller.rb new file mode 100644 index 000000000..c1db0d8e8 --- /dev/null +++ b/app/controllers/gestionnaires/activate_controller.rb @@ -0,0 +1,52 @@ +class Gestionnaires::ActivateController < ApplicationController + include TrustedDeviceConcern + + def new + @token = params[:token] + + user = User.with_reset_password_token(@token) + @gestionnaire = user&.gestionnaire + + if @gestionnaire + # the gestionnaire activates its account from an email + trust_device(Time.zone.now) + else + flash.alert = "Le lien de validation de gestionnaire a expiré, #{helpers.contact_link('contactez-nous', tags: 'lien expiré')} pour obtenir un nouveau lien." + redirect_to root_path + end + end + + def create + password = update_gestionnaire_params[:password] + + user = User.reset_password_by_token({ + password: password, + password_confirmation: password, + reset_password_token: update_gestionnaire_params[:reset_password_token] + }) + + if user&.errors&.empty? + sign_in(user, scope: :user) + + flash.notice = "Mot de passe enregistré" + redirect_to gestionnaire_groupe_gestionnaires_path + else + flash.alert = user.errors.full_messages + redirect_to gestionnaires_activate_path(token: update_gestionnaire_params[:reset_password_token]) + end + end + + private + + def update_gestionnaire_params + params.require(:gestionnaire).permit(:reset_password_token, :password) + end + + def try_to_authenticate(klass, email, password) + resource = klass.find_for_database_authentication(email: email) + + if resource&.valid_password?(password) + sign_in resource + end + end +end diff --git a/app/controllers/gestionnaires/groupe_gestionnaire_commentaires_controller.rb b/app/controllers/gestionnaires/groupe_gestionnaire_commentaires_controller.rb index c05faaf86..2ddd7cfdb 100644 --- a/app/controllers/gestionnaires/groupe_gestionnaire_commentaires_controller.rb +++ b/app/controllers/gestionnaires/groupe_gestionnaire_commentaires_controller.rb @@ -7,7 +7,9 @@ module Gestionnaires end def show + @commentaire_seen_at = current_gestionnaire.commentaire_seen_at(@groupe_gestionnaire, @last_commentaire.sender) @commentaire = CommentaireGroupeGestionnaire.new + current_gestionnaire.mark_commentaire_as_seen(@groupe_gestionnaire, @last_commentaire.sender) end def create @@ -30,7 +32,6 @@ module Gestionnaires else flash.alert = t('.alert_acl') end - # redirect_to gestionnaire_groupe_gestionnaire_commentaire_path(@groupe_gestionnaire, @last_commentaire) rescue Discard::RecordNotDiscarded flash.alert = t('.alert_already_discarded') end diff --git a/app/controllers/gestionnaires/groupe_gestionnaires_controller.rb b/app/controllers/gestionnaires/groupe_gestionnaires_controller.rb index cce7a147e..b10e2081d 100644 --- a/app/controllers/gestionnaires/groupe_gestionnaires_controller.rb +++ b/app/controllers/gestionnaires/groupe_gestionnaires_controller.rb @@ -7,6 +7,7 @@ module Gestionnaires end def show + @unread_commentaires = current_gestionnaire.unread_commentaires?(@groupe_gestionnaire) end def edit diff --git a/app/models/follow_commentaire_groupe_gestionnaire.rb b/app/models/follow_commentaire_groupe_gestionnaire.rb new file mode 100644 index 000000000..2179c326b --- /dev/null +++ b/app/models/follow_commentaire_groupe_gestionnaire.rb @@ -0,0 +1,7 @@ +class FollowCommentaireGroupeGestionnaire < ApplicationRecord + belongs_to :gestionnaire + belongs_to :groupe_gestionnaire + belongs_to :sender, polymorphic: true, optional: true + + validates :gestionnaire_id, uniqueness: { scope: [:groupe_gestionnaire_id, :sender_id, :sender_type, :unfollowed_at] } +end diff --git a/app/models/gestionnaire.rb b/app/models/gestionnaire.rb index 04f78964b..355659dea 100644 --- a/app/models/gestionnaire.rb +++ b/app/models/gestionnaire.rb @@ -2,6 +2,7 @@ class Gestionnaire < ApplicationRecord include UserFindByConcern has_and_belongs_to_many :groupe_gestionnaires has_many :commentaire_groupe_gestionnaires + has_many :follow_commentaire_groupe_gestionnaires belongs_to :user @@ -33,4 +34,27 @@ class Gestionnaire < ApplicationRecord 'Expiré' end end + + def unread_commentaires?(groupe_gestionnaire) + CommentaireGroupeGestionnaire + .joins(:groupe_gestionnaire) + .joins("LEFT JOIN follow_commentaire_groupe_gestionnaires ON follow_commentaire_groupe_gestionnaires.groupe_gestionnaire_id = commentaire_groupe_gestionnaires.groupe_gestionnaire_id AND follow_commentaire_groupe_gestionnaires.sender_id = commentaire_groupe_gestionnaires.sender_id AND follow_commentaire_groupe_gestionnaires.sender_type = commentaire_groupe_gestionnaires.sender_type AND follow_commentaire_groupe_gestionnaires.gestionnaire_id = #{self.id}") + .where(groupe_gestionnaire: groupe_gestionnaire) + .where('follow_commentaire_groupe_gestionnaires.commentaire_seen_at IS NULL OR follow_commentaire_groupe_gestionnaires.commentaire_seen_at < commentaire_groupe_gestionnaires.created_at') + .exists? + end + + def commentaire_seen_at(groupe_gestionnaire, sender) + FollowCommentaireGroupeGestionnaire + .where(gestionnaire: self, groupe_gestionnaire:, sender:) + .order(id: :desc) + .last + &.commentaire_seen_at + end + + def mark_commentaire_as_seen(groupe_gestionnaire, sender) + FollowCommentaireGroupeGestionnaire + .where(gestionnaire: self, groupe_gestionnaire: groupe_gestionnaire, sender: sender, unfollowed_at: nil) + .first_or_initialize.update(commentaire_seen_at: Time.zone.now) + end end diff --git a/app/models/groupe_gestionnaire.rb b/app/models/groupe_gestionnaire.rb index 31a30abfb..055b008f8 100644 --- a/app/models/groupe_gestionnaire.rb +++ b/app/models/groupe_gestionnaire.rb @@ -1,6 +1,7 @@ class GroupeGestionnaire < ApplicationRecord has_many :administrateurs has_many :commentaire_groupe_gestionnaires + has_many :follow_commentaire_groupe_gestionnaires has_and_belongs_to_many :gestionnaires has_ancestry diff --git a/app/views/administrateurs/groupe_gestionnaire/show.html.haml b/app/views/administrateurs/groupe_gestionnaire/show.html.haml index 076c24349..eebc4780a 100644 --- a/app/views/administrateurs/groupe_gestionnaire/show.html.haml +++ b/app/views/administrateurs/groupe_gestionnaire/show.html.haml @@ -3,8 +3,8 @@ metadatas: true } .fr-container - %h2= "Gestion du groupe gestionnaire № #{@groupe_gestionnaire.id}" + %h2= "Gestion du groupe gestionnaire \"#{@groupe_gestionnaire.name}\"" .fr-grid-row.fr-grid-row--gutters.fr-mb-5w - = render GroupeGestionnaire::Card::GestionnairesComponent.new(groupe_gestionnaire: @groupe_gestionnaire, path: admin_groupe_gestionnaire_gestionnaires_path) - = render GroupeGestionnaire::Card::AdministrateursComponent.new(groupe_gestionnaire: @groupe_gestionnaire, path: admin_groupe_gestionnaire_administrateurs_path) + = render GroupeGestionnaire::Card::GestionnairesComponent.new(groupe_gestionnaire: @groupe_gestionnaire, path: admin_groupe_gestionnaire_gestionnaires_path, is_gestionnaire: false) + = render GroupeGestionnaire::Card::AdministrateursComponent.new(groupe_gestionnaire: @groupe_gestionnaire, path: admin_groupe_gestionnaire_administrateurs_path, is_gestionnaire: false) = render GroupeGestionnaire::Card::CommentairesComponent.new(groupe_gestionnaire: @groupe_gestionnaire, administrateur: current_administrateur, path: admin_groupe_gestionnaire_commentaires_path) diff --git a/app/views/gestionnaires/activate/new.html.haml b/app/views/gestionnaires/activate/new.html.haml new file mode 100644 index 000000000..c020d67bc --- /dev/null +++ b/app/views/gestionnaires/activate/new.html.haml @@ -0,0 +1,26 @@ +- content_for(:title, t('.title')) + +- content_for :footer do + = render partial: "root/footer" + +.fr-container.fr-my-5w + .fr-grid-row.fr-grid-row--center + .fr-col-lg-6 + = form_for @gestionnaire, url: { controller: 'gestionnaires/activate', action: :create } do |f| + = f.hidden_field :reset_password_token, value: @token + + %fieldset.fr-mb-0.fr-fieldset{ aria: { labelledby: 'edit-password-legend' } } + %legend.fr-fieldset__legend#edit-password-legend + %h1.fr-h2= t('.title') + + .fr-fieldset__element + = render Dsfr::InputComponent.new(form: f, attribute: :email, opts: { disabled: true }) + + .fr-fieldset__element + = render Dsfr::InputComponent.new(form: f, attribute: :password, input_type: :password_field, + opts: { autofocus: 'true', autocomplete: 'new-password', data: { controller: 'turbo-input', turbo_input_url_value: show_password_complexity_path }}) + + #password_complexity + = render PasswordComplexityComponent.new + + = f.submit t('.continue'), id: 'submit-password', class: "fr-btn fr-btn--lg fr-mt-2w", data: { disable_with: t('views.users.passwords.edit.submit_loading') } diff --git a/app/views/gestionnaires/groupe_gestionnaire_commentaires/show.html.haml b/app/views/gestionnaires/groupe_gestionnaire_commentaires/show.html.haml index 1ced5e908..28188e1cb 100644 --- a/app/views/gestionnaires/groupe_gestionnaire_commentaires/show.html.haml +++ b/app/views/gestionnaires/groupe_gestionnaire_commentaires/show.html.haml @@ -10,6 +10,6 @@ %ul.messages-list{ data: { controller: 'scroll-to' } } - @groupe_gestionnaire.commentaire_groupe_gestionnaires.where(sender_id: @last_commentaire.sender_id, sender_type: @last_commentaire.sender_type).each do |commentaire| %li.message{ class: commentaire_is_from_me_class(commentaire, current_gestionnaire), id: dom_id(commentaire) } - = render(GroupeGestionnaire::GroupeGestionnaireCommentaires::CommentaireComponent.new(commentaire: commentaire, connected_user: current_gestionnaire)) + = render(GroupeGestionnaire::GroupeGestionnaireCommentaires::CommentaireComponent.new(commentaire: commentaire, connected_user: current_gestionnaire, commentaire_seen_at: @commentaire_seen_at)) - if @last_commentaire.sender = render partial: "shared/groupe_gestionnaires/commentaires/form", locals: { commentaire: @commentaire, form_url: gestionnaire_groupe_gestionnaire_commentaires_path(@groupe_gestionnaire) } diff --git a/app/views/gestionnaires/groupe_gestionnaires/show.html.haml b/app/views/gestionnaires/groupe_gestionnaires/show.html.haml index 60d22bda5..5cd67d921 100644 --- a/app/views/gestionnaires/groupe_gestionnaires/show.html.haml +++ b/app/views/gestionnaires/groupe_gestionnaires/show.html.haml @@ -13,14 +13,14 @@ = t('views.gestionnaires.groupe_gestionnaires.delete') .fr-container - %h2= "Gestion du groupe gestionnaire № #{@groupe_gestionnaire.id}" + %h2= "Gestion du groupe gestionnaire \"#{@groupe_gestionnaire.name}\"" - if @groupe_gestionnaire.groupe_gestionnaire_id.present? %p groupe parent : %a{ href: gestionnaire_groupe_gestionnaire_path(@groupe_gestionnaire.groupe_gestionnaire) }= @groupe_gestionnaire.groupe_gestionnaire.name - .fr-grid-row.fr-grid-row--gutters.fr-mb-5w + .fr-grid-row.fr-grid-row--gutters.fr-mb-5w.fr-groupe_gestionnaire_cards = render GroupeGestionnaire::Card::GestionnairesComponent.new(groupe_gestionnaire: @groupe_gestionnaire, path: gestionnaire_groupe_gestionnaire_gestionnaires_path(@groupe_gestionnaire)) = render GroupeGestionnaire::Card::AdministrateursComponent.new(groupe_gestionnaire: @groupe_gestionnaire, path: gestionnaire_groupe_gestionnaire_administrateurs_path(@groupe_gestionnaire)) = render GroupeGestionnaire::Card::ChildrenComponent.new(groupe_gestionnaire: @groupe_gestionnaire, path: gestionnaire_groupe_gestionnaire_children_path(@groupe_gestionnaire)) - = render GroupeGestionnaire::Card::CommentairesComponent.new(groupe_gestionnaire: @groupe_gestionnaire, administrateur: nil, path: gestionnaire_groupe_gestionnaire_commentaires_path(@groupe_gestionnaire)) + = render GroupeGestionnaire::Card::CommentairesComponent.new(groupe_gestionnaire: @groupe_gestionnaire, administrateur: nil, path: gestionnaire_groupe_gestionnaire_commentaires_path(@groupe_gestionnaire), unread_commentaires: @unread_commentaires) diff --git a/app/views/user_mailer/invite_gestionnaire.html.haml b/app/views/user_mailer/invite_gestionnaire.html.haml index 578e6777b..267f2add4 100644 --- a/app/views/user_mailer/invite_gestionnaire.html.haml +++ b/app/views/user_mailer/invite_gestionnaire.html.haml @@ -8,6 +8,6 @@ %p Votre compte a été créé pour l'adresse email #{@user.email}. Pour l’activer, nous vous invitons à cliquer sur le lien suivant :  - = link_to(users_activate_url(token: @reset_password_token), users_activate_url(token: @reset_password_token)) + = link_to(gestionnaires_activate_url(token: @reset_password_token), gestionnaires_activate_url(token: @reset_password_token)) = render partial: "layouts/mailers/signature" diff --git a/config/locales/en.yml b/config/locales/en.yml index e72cd0a1c..9f06600b3 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -797,6 +797,11 @@ en: updated_at: updated at closed_at: closed at auto_archive_on: will close at + gestionnaires: + activate: + new: + title: Pick a password + continue: Continue users: dossiers: test_procedure: "This file is submitted on a test procedure. Any modification of the procedure by the administrator (addition of a field, publication of the procedure, etc.) will result in the removal of the file." diff --git a/config/locales/fr.yml b/config/locales/fr.yml index f25ffbf20..c32ff20a9 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -895,6 +895,11 @@ fr: explication_html: "

API Particulier facilite l’accès des administrations aux données familiales (CAF), aux données fiscales (DGFiP), au statut pôle-emploi et au statut étudiant d’un citoyen pour simplifier les démarches administratives mises en œuvre par les collectivités et les administrations.
Cela permet aux administrations d’accéder à des informations certifiées à la source et ainsi :

Important : les disposition de l’article L144-8 n’autorisent que l’échange des informations strictement nécessaires pour traiter une démarche.

En conséquence, ne sélectionnez ici que les données auxquelles vous aurez accès d’un point de vue légal.

" update: sources_ok: 'Mise à jour effectuée' + gestionnaires: + activate: + new: + title: Choix du mot de passe + continue: Continuer zones: ministeres: Ministères france_connect: diff --git a/config/routes.rb b/config/routes.rb index 8d6ad9295..77a8e89d9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -517,6 +517,11 @@ Rails.application.routes.draw do resources :commentaires, controller: 'groupe_gestionnaire_commentaires', only: [:index, :show, :create, :destroy] end end + + namespace :gestionnaires do + get 'activate' => '/gestionnaires/activate#new' + patch 'activate' => '/gestionnaires/activate#create' + end end # diff --git a/db/migrate/20231027124906_create_follow_commentaire_groupe_gestionnaires.rb b/db/migrate/20231027124906_create_follow_commentaire_groupe_gestionnaires.rb new file mode 100644 index 000000000..0e8eaeded --- /dev/null +++ b/db/migrate/20231027124906_create_follow_commentaire_groupe_gestionnaires.rb @@ -0,0 +1,16 @@ +class CreateFollowCommentaireGroupeGestionnaires < ActiveRecord::Migration[6.1] + disable_ddl_transaction! + + def change + create_table "follow_commentaire_groupe_gestionnaires" do |t| + t.references :groupe_gestionnaire, index: { name: :index_follow_commentaire_on_groupe_gestionnaire } + t.references :gestionnaire, null: false, index: { name: :index_follow_commentaire_on_gestionnaire } + t.string "sender_type", null: true + t.bigint "sender_id", null: true + t.datetime "commentaire_seen_at" + t.datetime "unfollowed_at" + t.timestamps + t.index [:gestionnaire_id, :groupe_gestionnaire_id, :sender_id, :sender_type, :unfollowed_at], name: :index_follow_commentaire_on_groupe_gestionnaire_unfollow, unique: true + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 73bdb748d..66c256dd0 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -609,6 +609,20 @@ ActiveRecord::Schema[7.0].define(version: 2024_01_23_085909) do t.index ["feature_key", "key", "value"], name: "index_flipper_gates_on_feature_key_and_key_and_value", unique: true end + create_table "follow_commentaire_groupe_gestionnaires", force: :cascade do |t| + t.datetime "commentaire_seen_at", precision: 6 + t.datetime "created_at", precision: 6, null: false + t.bigint "gestionnaire_id", null: false + t.bigint "groupe_gestionnaire_id" + t.bigint "sender_id" + t.string "sender_type" + t.datetime "unfollowed_at", precision: 6, precision: nil + t.datetime "updated_at", precision: 6, null: false + t.index ["gestionnaire_id", "groupe_gestionnaire_id", "sender_id", "sender_type", "unfollowed_at"], name: "index_follow_commentaire_on_groupe_gestionnaire_unfollow", unique: true + t.index ["gestionnaire_id"], name: "index_follow_commentaire_on_gestionnaire" + t.index ["groupe_gestionnaire_id"], name: "index_follow_commentaire_on_groupe_gestionnaire" + end + create_table "follows", id: :serial, force: :cascade do |t| t.datetime "annotations_privees_seen_at", precision: nil, null: false t.datetime "avis_seen_at", precision: nil, null: false diff --git a/spec/controllers/administrateurs/activate_controller_spec.rb b/spec/controllers/administrateurs/activate_controller_spec.rb index 6b97bfe4a..b1f0ee627 100644 --- a/spec/controllers/administrateurs/activate_controller_spec.rb +++ b/spec/controllers/administrateurs/activate_controller_spec.rb @@ -17,4 +17,31 @@ describe Administrateurs::ActivateController, type: :controller do it { expect(controller).not_to have_received(:trust_device) } end end + + describe '#create' do + let!(:administrateur) { create(:administrateur) } + let(:token) { administrateur.user.send(:set_reset_password_token) } + let(:password) { 'Another-password-ok!@#123?' } + + before { post :create, params: { administrateur: { reset_password_token: token, password: password } } } + + context 'when the token is ok' do + it { expect(administrateur.user.reload.valid_password?(password)).to be true } + it { expect(response).to redirect_to(admin_procedures_path) } + end + + context 'when the password is not strong' do + let(:password) { 'another-password-ok?' } + + it { expect(administrateur.user.reload.valid_password?(password)).to be false } + it { expect(response).to redirect_to(admin_activate_path(token: token)) } + end + + context 'when the token is bad' do + let(:token) { 'bad' } + + it { expect(administrateur.user.reload.valid_password?(password)).to be false } + it { expect(response).to redirect_to(admin_activate_path(token: token)) } + end + end end diff --git a/spec/controllers/gestionnaires/activate_controller_spec.rb b/spec/controllers/gestionnaires/activate_controller_spec.rb new file mode 100644 index 000000000..c2fc2ad46 --- /dev/null +++ b/spec/controllers/gestionnaires/activate_controller_spec.rb @@ -0,0 +1,40 @@ +describe Gestionnaires::ActivateController, type: :controller do + describe '#new' do + let(:gestionnaire) { create(:gestionnaire) } + let(:token) { gestionnaire.user.send(:set_reset_password_token) } + + before { allow(controller).to receive(:trust_device) } + + context 'when the token is ok' do + before { get :new, params: { token: token } } + + it { expect(controller).to have_received(:trust_device) } + end + + context 'when the token is bad' do + before { get :new, params: { token: 'bad' } } + + it { expect(controller).not_to have_received(:trust_device) } + end + end + + describe '#create' do + let!(:gestionnaire) { create(:gestionnaire) } + let(:token) { gestionnaire.user.send(:set_reset_password_token) } + let(:password) { 'another-password-ok?' } + + before { post :create, params: { gestionnaire: { reset_password_token: token, password: password } } } + + context 'when the token is ok' do + it { expect(gestionnaire.user.reload.valid_password?(password)).to be true } + it { expect(response).to redirect_to(gestionnaire_groupe_gestionnaires_path) } + end + + context 'when the token is bad' do + let(:token) { 'bad' } + + it { expect(gestionnaire.user.reload.valid_password?(password)).to be false } + it { expect(response).to redirect_to(gestionnaires_activate_path(token: token)) } + end + end +end diff --git a/spec/controllers/gestionnaires/groupe_gestionnaire_commentaires_controller_spec.rb b/spec/controllers/gestionnaires/groupe_gestionnaire_commentaires_controller_spec.rb index 54b00c08f..e6b659598 100644 --- a/spec/controllers/gestionnaires/groupe_gestionnaire_commentaires_controller_spec.rb +++ b/spec/controllers/gestionnaires/groupe_gestionnaire_commentaires_controller_spec.rb @@ -4,7 +4,7 @@ describe Gestionnaires::GroupeGestionnaireCommentairesController, type: :control let(:groupe_gestionnaire) { create(:groupe_gestionnaire, gestionnaires: [gestionnaire], administrateurs: [administrateur]) } let!(:commentaire) { create(:commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire, sender: administrateur) } - describe "yyyy#index" do + describe "#index" do render_views subject { get :index, params: { groupe_gestionnaire_id: groupe_gestionnaire.id } } @@ -27,7 +27,7 @@ describe Gestionnaires::GroupeGestionnaireCommentairesController, type: :control end end - describe "yyyy#show" do + describe "#show" do render_views subject { get :show, params: { groupe_gestionnaire_id: groupe_gestionnaire.id, id: commentaire.id } } @@ -50,7 +50,7 @@ describe Gestionnaires::GroupeGestionnaireCommentairesController, type: :control end end - describe "yyyy#create" do + describe "#create" do before do sign_in(gestionnaire.user) post :create, @@ -69,7 +69,7 @@ describe Gestionnaires::GroupeGestionnaireCommentairesController, type: :control end end - describe "yyyy#destroy" do + describe "#destroy" do before do sign_in(gestionnaire.user) end diff --git a/spec/factories/follow_commentaire_groupe_gestionnaire.rb b/spec/factories/follow_commentaire_groupe_gestionnaire.rb new file mode 100644 index 000000000..a8db7c133 --- /dev/null +++ b/spec/factories/follow_commentaire_groupe_gestionnaire.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :follow_commentaire_groupe_gestionnaire do + association :groupe_gestionnaire + association :gestionnaire + end +end diff --git a/spec/models/follow_commentaire_groupe_gestionnaire_spec.rb b/spec/models/follow_commentaire_groupe_gestionnaire_spec.rb new file mode 100644 index 000000000..7e9fca948 --- /dev/null +++ b/spec/models/follow_commentaire_groupe_gestionnaire_spec.rb @@ -0,0 +1,7 @@ +describe FollowCommentaireGroupeGestionnaire, type: :model do + describe 'associations' do + it { is_expected.to belong_to(:gestionnaire) } + it { is_expected.to belong_to(:groupe_gestionnaire) } + it { is_expected.to belong_to(:sender).optional } + end +end diff --git a/spec/models/gestionnaire_spec.rb b/spec/models/gestionnaire_spec.rb index bffbc3eab..d26be8655 100644 --- a/spec/models/gestionnaire_spec.rb +++ b/spec/models/gestionnaire_spec.rb @@ -1,6 +1,7 @@ describe Gestionnaire, type: :model do describe 'associations' do it { is_expected.to have_many(:commentaire_groupe_gestionnaires) } + it { is_expected.to have_many(:follow_commentaire_groupe_gestionnaires) } it { is_expected.to have_and_belong_to_many(:groupe_gestionnaires) } end @@ -78,4 +79,111 @@ describe Gestionnaire, type: :model do it { is_expected.to eq [gestionnaire] } end end + + describe "#unread_commentaires?" do + context "over three different groupe_gestionnaire" do + let(:gestionnaire) { create(:gestionnaire) } + let(:administrateur) { create(:administrateur) } + let(:groupe_gestionnaire) { create(:groupe_gestionnaire, gestionnaires: [gestionnaire]) } + let!(:commentaire_groupe_gestionnaire) { create(:commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire, sender: administrateur, created_at: 12.hours.ago) } + let!(:follow_commentaire_groupe_gestionnaire) { create(:follow_commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire, gestionnaire: gestionnaire, sender: administrateur, commentaire_seen_at: Time.zone.now) } + + let(:gestionnaire_unread_commentaire_cause_never_seen) { create(:gestionnaire) } + let(:administrateur_unread_commentaire_cause_never_seen) { create(:administrateur) } + let(:groupe_gestionnaire_unread_commentaire_cause_never_seen) { create(:groupe_gestionnaire, gestionnaires: [gestionnaire_unread_commentaire_cause_never_seen]) } + let!(:commentaire_groupe_gestionnaire_unread_commentaire_cause_never_seen) { create(:commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire_unread_commentaire_cause_never_seen, sender: administrateur_unread_commentaire_cause_never_seen, created_at: 12.hours.ago) } + + let(:gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire) { create(:gestionnaire) } + let(:administrateur_unread_commentaire_cause_seen_at_before_last_commentaire) { create(:administrateur) } + let(:groupe_gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire) { create(:groupe_gestionnaire, gestionnaires: [gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire]) } + let!(:commentaire_groupe_gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire) { create(:commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire, sender: administrateur_unread_commentaire_cause_seen_at_before_last_commentaire, created_at: 12.hours.ago) } + let!(:follow_commentaire_groupe_gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire) { create(:follow_commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire, gestionnaire: gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire, sender: administrateur_unread_commentaire_cause_seen_at_before_last_commentaire, commentaire_seen_at: 1.day.ago) } + + it do + expect(gestionnaire.unread_commentaires?(groupe_gestionnaire)).to eq false + expect(gestionnaire_unread_commentaire_cause_never_seen.unread_commentaires?(groupe_gestionnaire_unread_commentaire_cause_never_seen)).to eq true + expect(gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire.unread_commentaires?(groupe_gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire)).to eq true + end + end + + context "over same groupe_gestionnaire" do + let(:gestionnaire) { create(:gestionnaire) } + let(:gestionnaire_unread_commentaire_cause_never_seen) { create(:gestionnaire) } + let(:gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire) { create(:gestionnaire) } + let(:administrateur) { create(:administrateur) } + let(:groupe_gestionnaire) { create(:groupe_gestionnaire, gestionnaires: [gestionnaire, gestionnaire_unread_commentaire_cause_never_seen, gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire]) } + let!(:commentaire_groupe_gestionnaire) { create(:commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire, sender: administrateur, created_at: 12.hours.ago) } + + let!(:follow_commentaire_groupe_gestionnaire) { create(:follow_commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire, gestionnaire: gestionnaire, sender: administrateur, commentaire_seen_at: Time.zone.now) } + let!(:follow_commentaire_groupe_gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire) { create(:follow_commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire, gestionnaire: gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire, sender: administrateur, commentaire_seen_at: 1.day.ago) } + + it do + expect(gestionnaire.unread_commentaires?(groupe_gestionnaire)).to eq false + expect(gestionnaire_unread_commentaire_cause_never_seen.unread_commentaires?(groupe_gestionnaire)).to eq true + expect(gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire.unread_commentaires?(groupe_gestionnaire)).to eq true + end + end + end + + describe "#commentaire_seen_at" do + context "when already seen commentaire" do + let(:gestionnaire) { create(:gestionnaire) } + let(:administrateur) { create(:administrateur) } + let(:groupe_gestionnaire) { create(:groupe_gestionnaire, gestionnaires: [gestionnaire]) } + let!(:commentaire_groupe_gestionnaire) { create(:commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire, sender: administrateur, created_at: 12.hours.ago) } + let!(:follow_commentaire_groupe_gestionnaire) { create(:follow_commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire, gestionnaire: gestionnaire, sender: administrateur, commentaire_seen_at: Time.zone.now) } + + it { expect(gestionnaire.commentaire_seen_at(groupe_gestionnaire, administrateur).to_date).to eq Date.current } + end + + context "when never seen commentaire" do + let(:gestionnaire) { create(:gestionnaire) } + let(:administrateur) { create(:administrateur) } + let(:groupe_gestionnaire) { create(:groupe_gestionnaire, gestionnaires: [gestionnaire]) } + let!(:commentaire_groupe_gestionnaire) { create(:commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire, sender: administrateur, created_at: 12.hours.ago) } + + it { expect(gestionnaire.commentaire_seen_at(groupe_gestionnaire, administrateur)).to eq nil } + end + end + + describe "#mark_commentaire_as_seen" do + context "when already seen commentaire" do + let(:now) { Time.zone.now.beginning_of_minute } + let(:gestionnaire) { create(:gestionnaire) } + let(:administrateur) { create(:administrateur) } + let(:groupe_gestionnaire) { create(:groupe_gestionnaire, gestionnaires: [gestionnaire]) } + let!(:commentaire_groupe_gestionnaire) { create(:commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire, sender: administrateur, created_at: 12.hours.ago) } + let!(:follow_commentaire_groupe_gestionnaire) { create(:follow_commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire, gestionnaire: gestionnaire, sender: administrateur, commentaire_seen_at: 12.hours.ago) } + + subject do + travel_to(now) do + gestionnaire.mark_commentaire_as_seen(groupe_gestionnaire, administrateur) + end + end + + it do + subject + expect(gestionnaire.commentaire_seen_at(groupe_gestionnaire, administrateur)).to eq now + end + end + + context "when never seen commentaire" do + let(:now) { Time.zone.now.beginning_of_minute } + let(:gestionnaire) { create(:gestionnaire) } + let(:administrateur) { create(:administrateur) } + let(:groupe_gestionnaire) { create(:groupe_gestionnaire, gestionnaires: [gestionnaire]) } + let!(:commentaire_groupe_gestionnaire) { create(:commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire, sender: administrateur, created_at: 12.hours.ago) } + + subject do + travel_to(now) do + gestionnaire.mark_commentaire_as_seen(groupe_gestionnaire, administrateur) + end + end + + it do + subject + expect(gestionnaire.commentaire_seen_at(groupe_gestionnaire, administrateur)).to eq now + end + end + end end diff --git a/spec/models/groupe_gestionnaire_spec.rb b/spec/models/groupe_gestionnaire_spec.rb index caf8396bf..eea888f71 100644 --- a/spec/models/groupe_gestionnaire_spec.rb +++ b/spec/models/groupe_gestionnaire_spec.rb @@ -2,6 +2,7 @@ describe GroupeGestionnaire, type: :model do describe 'associations' do it { is_expected.to have_many(:administrateurs) } it { is_expected.to have_many(:commentaire_groupe_gestionnaires) } + it { is_expected.to have_many(:follow_commentaire_groupe_gestionnaires) } it { is_expected.to have_and_belong_to_many(:gestionnaires) } end