diff --git a/.gitignore b/.gitignore index 7a0e072c2c..9bb47e7663 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,7 @@ coverage # Ignore editors .vscode +mise.toml # Instruments IAc AGENTS.md diff --git a/app/controllers/admin/communication/extranets/alumni_controller.rb b/app/controllers/admin/communication/extranets/alumni_controller.rb index 5dad9657e8..02e32ad4c2 100644 --- a/app/controllers/admin/communication/extranets/alumni_controller.rb +++ b/app/controllers/admin/communication/extranets/alumni_controller.rb @@ -27,7 +27,7 @@ def index def send_invitation person = @extranet.alumni.find(params[:id]) unless person.user_id - ExtranetMailer.invitation_message(@extranet, person).deliver_later + ExtranetMailer.invitation_message_automatic(@extranet, person).deliver_later person.update_column(:invitation_sent_at, Time.current) redirect_to admin_communication_extranet_alumni_path(@extranet), notice: t('admin.communication.extranet.alumni.send_invitation.just_sent', name: person.to_s_in(current_language)) diff --git a/app/controllers/admin/communication/extranets_controller.rb b/app/controllers/admin/communication/extranets_controller.rb index 97067e6829..0b40028ade 100644 --- a/app/controllers/admin/communication/extranets_controller.rb +++ b/app/controllers/admin/communication/extranets_controller.rb @@ -101,8 +101,11 @@ def base_localization_params :favicon_delete, :home_sentence, :id, - :invitation_message_subject, - :invitation_message_text, + :invitation_message_automatic_subject, + :invitation_message_automatic_text, + :invitation_message_manual_subject, + :invitation_message_manual_text, + :invitation_message_manual_signature, :language_id, :logo, :logo_delete, diff --git a/app/controllers/extranet/alumni/persons/invitations_controller.rb b/app/controllers/extranet/alumni/persons/invitations_controller.rb new file mode 100644 index 0000000000..78222473d2 --- /dev/null +++ b/app/controllers/extranet/alumni/persons/invitations_controller.rb @@ -0,0 +1,64 @@ +class Extranet::Alumni::Persons::InvitationsController < Extranet::Alumni::ApplicationController + + before_action :find_person, :ensure_person_is_invitable + + def new + @invitation = current_extranet.invitations.build( + from_name: current_user.to_s, + from_email: current_user.email, + to_name: @l10n.to_s, + to_email: @person.email, + message: default_message + ) + breadcrumb + end + + def create + @invitation = current_extranet.invitations.build(invitation_params) + if @invitation.save + redirect_to [:alumni, @person], notice: t('extranet.alumni.invitations.created') + else + breadcrumb + render :new, status: :unprocessable_content + end + end + + protected + + def invitation_params + params.require(:communication_extranet_invitation) + .permit(:from_name, :from_email, :to_name, :to_email, :message) + .merge( + user_id: current_user.id, + person_id: @person.id, + university_id: current_university.id + ) + end + + def breadcrumb + super + add_breadcrumb University::Person.model_name.human(count: 2), alumni_university_persons_path + add_breadcrumb @l10n, alumni_university_person_path(@person) + add_breadcrumb t('extranet.alumni.invitations.title') + end + + def find_person + @person = current_extranet.alumni.find(params[:id]) + @l10n = @person.best_localization_for(current_language) + end + + def ensure_person_is_invitable + unless Communication::Extranet::Invitation.sendable_to?(@person) + redirect_to [:alumni, @person], alert: t('extranet.alumni.invitations.send.too_soon') + end + end + + def default_message + current_extranet_l10n.invitation_manual_text( + from_name: current_user.to_s, + from_years: current_user.person&.diploma_years_sentence, + to_name: @l10n.to_s + ) + end + +end diff --git a/app/controllers/extranet/alumni/persons_controller.rb b/app/controllers/extranet/alumni/persons_controller.rb index 0b51734907..6f36c09fe8 100644 --- a/app/controllers/extranet/alumni/persons_controller.rb +++ b/app/controllers/extranet/alumni/persons_controller.rb @@ -1,4 +1,5 @@ class Extranet::Alumni::PersonsController < Extranet::Alumni::ApplicationController + def index @facets = University::Person::Alumnus::Facets.new params[:facets], { model: current_extranet.about.university_person_alumni, @@ -36,4 +37,5 @@ def breadcrumb super add_breadcrumb University::Person.model_name.human(count: 2), alumni_university_persons_path end + end diff --git a/app/controllers/users/registrations_controller.rb b/app/controllers/users/registrations_controller.rb index 6e6d6452a4..cb22cb3712 100644 --- a/app/controllers/users/registrations_controller.rb +++ b/app/controllers/users/registrations_controller.rb @@ -10,6 +10,12 @@ class Users::RegistrationsController < Devise::RegistrationsController before_action :configure_account_update_params, only: :update before_action :confirm_two_factor_authenticated, except: [:new, :create, :cancel] + def new + super do |resource| + prefill_from_invitation(resource) + end + end + def edit # this action is not used anymore, replaced for both universities and extranets. # so we redirect to the appropriate profile edition @@ -23,6 +29,28 @@ def edit protected + def build_resource(hash = {}) + super + resource.invitation = invitation + end + + def prefill_from_invitation(resource) + return if invitation.blank? + person_l10n = invitation.person&.best_localization_for(current_language) + resource.assign_attributes( + first_name: person_l10n&.first_name, + last_name: person_l10n&.last_name, + email: invitation.to_email, + mobile_phone: invitation.person&.phone_mobile + ) + end + + def invitation + return @invitation if defined?(@invitation) + token = params[:invitation_token] + @invitation = token.present? ? current_extranet&.invitations&.pending&.find_by(token: token) : nil + end + def sign_up(resource_name, resource) sign_in(resource, event: :authentication) end diff --git a/app/mailers/extranet_mailer.rb b/app/mailers/extranet_mailer.rb index 6fbab626cc..06a176d042 100644 --- a/app/mailers/extranet_mailer.rb +++ b/app/mailers/extranet_mailer.rb @@ -2,7 +2,7 @@ class ExtranetMailer < ApplicationMailer helper :application # Gives access to all helpers defined within `application_helper` default template_path: 'mailers/extranet' - def invitation_message(extranet, person) + def invitation_message_automatic(extranet, person) @extranet = extranet @university = @extranet.university @person = person @@ -17,8 +17,48 @@ def invitation_message(extranet, person) I18n.with_locale(@language.iso_code.to_sym) do mail from: @university.mail_from[:full], to: @email, - subject: @l10n.invitation_message_subject if should_send?(@email) + subject: @l10n.invitation_message_automatic_subject if should_send?(@email) end end + def invitation_message_manual(invitation) + @invitation = invitation + extranet = @invitation.extranet + @university = extranet.university + language = @invitation.person&.user&.language || @university.default_language + extranet_l10n = extranet.best_localization_for(language) + @signature = invitation_manual_signature(extranet_l10n) + @registration_url = new_user_registration_url( + host: extranet.host, + invitation_token: @invitation.token + ) + + merge_with_university_infos(@university, {}) + + I18n.with_locale(language.iso_code.to_sym) do + mail from: @university.mail_from[:full], + to: @invitation.to_email, + subject: invitation_manual_subject(extranet_l10n) if should_send?(@invitation.to_email) + end + end + + private + + def invitation_manual_subject(extranet_l10n) + extranet_l10n.invitation_manual_subject(**invitation_substitutions) + end + + def invitation_manual_signature(extranet_l10n) + extranet_l10n.invitation_manual_signature(**invitation_substitutions) + end + + def invitation_substitutions + { + from_name: @invitation.from_name, + from_years: @invitation.user.person&.diploma_years_sentence, + to_name: @invitation.to_name + } + end + + end diff --git a/app/models/communication/block.rb b/app/models/communication/block.rb index 816a08f2dd..8127307087 100644 --- a/app/models/communication/block.rb +++ b/app/models/communication/block.rb @@ -11,7 +11,7 @@ # migration_identifier :string # position :integer not null # published :boolean default(TRUE) -# template_kind :integer default(NULL), not null, indexed => [university_id] +# template_kind :integer default(0), not null, indexed => [university_id] # title :string # created_at :datetime not null # updated_at :datetime not null diff --git a/app/models/communication/extranet.rb b/app/models/communication/extranet.rb index 1435b2870a..fa3400a78b 100644 --- a/app/models/communication/extranet.rb +++ b/app/models/communication/extranet.rb @@ -17,7 +17,7 @@ # sso_cert :text # sso_mapping :jsonb # sso_name_identifier_format :string -# sso_provider :integer default("saml") +# sso_provider :integer default(0) # sso_target_url :string # upper_menu :text default("") # created_at :datetime not null @@ -67,6 +67,7 @@ class Communication::Extranet < ApplicationRecord has_many :documents has_many :document_categories, class_name: 'Communication::Extranet::Document::Category' has_many :document_kinds, class_name: 'Communication::Extranet::Document::Kind' + has_many :invitations, class_name: 'Communication::Extranet::Invitation', dependent: :destroy validates :host, presence: true diff --git a/app/models/communication/extranet/connection.rb b/app/models/communication/extranet/connection.rb index d35791c052..d7e492f413 100644 --- a/app/models/communication/extranet/connection.rb +++ b/app/models/communication/extranet/connection.rb @@ -37,7 +37,7 @@ def self.permitted_about_classes def send_invitation_to_person # Do not send invitation if there is no email on the person's user or the person itself return unless about.user.try(:email).present? || about.email.present? - ExtranetMailer.invitation_message(extranet, about).deliver_later + ExtranetMailer.invitation_message_automatic(extranet, about).deliver_later about.update_column(:invitation_sent_at, Time.current) end end diff --git a/app/models/communication/extranet/invitation.rb b/app/models/communication/extranet/invitation.rb new file mode 100644 index 0000000000..0843230ba3 --- /dev/null +++ b/app/models/communication/extranet/invitation.rb @@ -0,0 +1,66 @@ +# == Schema Information +# +# Table name: communication_extranet_invitations +# +# id :uuid not null, primary key +# from_email :string +# from_name :string +# message :text +# to_email :string +# to_name :string +# token :string uniquely indexed +# created_at :datetime not null +# updated_at :datetime not null +# extranet_id :uuid indexed +# person_id :uuid indexed +# university_id :uuid indexed +# user_id :uuid indexed +# +# Indexes +# +# index_communication_extranet_invitations_on_extranet_id (extranet_id) +# index_communication_extranet_invitations_on_person_id (person_id) +# index_communication_extranet_invitations_on_token (token) UNIQUE +# index_communication_extranet_invitations_on_university_id (university_id) +# index_communication_extranet_invitations_on_user_id (user_id) +# +# Foreign Keys +# +# fk_rails_2a0ba0dd0d (university_id => universities.id) +# fk_rails_85c1d16bbf (user_id => users.id) +# fk_rails_989e9a94ca (person_id => university_people.id) +# fk_rails_e064970cc5 (extranet_id => communication_extranets.id) +# +class Communication::Extranet::Invitation < ApplicationRecord + include HasUniversity + include Sanitizable + + has_secure_token :token + + belongs_to :extranet, class_name: 'Communication::Extranet' + belongs_to :user + belongs_to :person, class_name: 'University::Person' + validates :from_name, :from_email, :to_name, :to_email, :message, presence: true + validate :can_send_to_person, on: :create + validates :to_email, :from_email, format: { with: Devise.email_regexp } + + after_create_commit :send_invitation_email + + scope :pending, -> { joins(:person).where(university_people: { user_id: nil }) } + + def self.sendable_to?(person) + return false if person.user.present? + self.where(person: person).where('created_at >= ?', University::Person::WithAlumnus::DELAY_FOR_INVITATION.ago).none? + end + + private + + def can_send_to_person + errors.add(:to_email, :too_soon) unless self.class.sendable_to?(person) + end + + def send_invitation_email + ExtranetMailer.invitation_message_manual(self).deliver_later + end + +end diff --git a/app/models/communication/extranet/localization.rb b/app/models/communication/extranet/localization.rb index a9f30cea87..aad97c1501 100644 --- a/app/models/communication/extranet/localization.rb +++ b/app/models/communication/extranet/localization.rb @@ -2,23 +2,26 @@ # # Table name: communication_extranet_localizations # -# id :uuid not null, primary key -# cookies_policy :text -# home_sentence :text -# invitation_message_subject :string default("") -# invitation_message_text :text default("") -# name :string -# privacy_policy :text -# published :boolean default(FALSE) -# published_at :datetime -# registration_contact :string -# sso_button_label :string -# terms :text -# created_at :datetime not null -# updated_at :datetime not null -# about_id :uuid uniquely indexed => [language_id], indexed -# language_id :uuid uniquely indexed => [about_id], indexed -# university_id :uuid indexed +# id :uuid not null, primary key +# cookies_policy :text +# home_sentence :text +# invitation_message_automatic_subject :string default("") +# invitation_message_automatic_text :text default("") +# invitation_message_manual_signature :text +# invitation_message_manual_subject :string +# invitation_message_manual_text :text +# name :string +# privacy_policy :text +# published :boolean default(FALSE) +# published_at :datetime +# registration_contact :string +# sso_button_label :string +# terms :text +# created_at :datetime not null +# updated_at :datetime not null +# about_id :uuid uniquely indexed => [language_id], indexed +# language_id :uuid uniquely indexed => [about_id], indexed +# university_id :uuid indexed # # Indexes # @@ -49,7 +52,7 @@ class Communication::Extranet::Localization < ApplicationRecord attachable.variant :thumb, resize_to_limit: [228, 228] end - before_validation :set_default_invitation_message + before_validation :set_default_invitation_messages validates :name, presence: true validates :logo, size: { less_than: 1.megabytes } @@ -60,17 +63,42 @@ def to_s "#{name}" end + def invitation_manual_subject(from_name:, from_years:, to_name:) + substitute_invitation_placeholders(invitation_message_manual_subject, from_name: from_name, from_years: from_years, to_name: to_name) + end + + def invitation_manual_text(from_name:, from_years:, to_name:) + substitute_invitation_placeholders(invitation_message_manual_text, from_name: from_name, from_years: from_years, to_name: to_name) + end + + def invitation_manual_signature(from_name:, from_years:, to_name:) + substitute_invitation_placeholders(invitation_message_manual_signature, from_name: from_name, from_years: from_years, to_name: to_name) + end + protected + def substitute_invitation_placeholders(text, from_name:, from_years:, to_name:) + substitutions = { + 'name' => to_name, + 'sender_name' => from_name, + 'sender_years' => from_years + } + substitutions.each { |key, value| text = text.gsub("{{#{key}}}", value.to_s) } + text + end + def prevent_unpublishing_default_language return unless about.default_language_id == language_id return if published? errors.add(:published, :cannot_unpublished_default) end - def set_default_invitation_message - self.invitation_message_subject = I18n.t('mailers.extranet.invitation_message.subject') if self.invitation_message_subject.blank? - self.invitation_message_text = I18n.t('mailers.extranet.invitation_message.text') if self.invitation_message_text.blank? + def set_default_invitation_messages + self.invitation_message_automatic_subject = I18n.t('mailers.extranet.invitation_messages.automatic.subject', locale: language.iso_code) if self.invitation_message_automatic_subject.blank? + self.invitation_message_automatic_text = I18n.t('mailers.extranet.invitation_messages.automatic.text', locale: language.iso_code) if self.invitation_message_automatic_text.blank? + self.invitation_message_manual_subject = I18n.t('mailers.extranet.invitation_messages.manual.subject', locale: language.iso_code) if self.invitation_message_manual_subject.blank? + self.invitation_message_manual_text = I18n.t('mailers.extranet.invitation_messages.manual.text', locale: language.iso_code) if self.invitation_message_manual_text.blank? + self.invitation_message_manual_signature = I18n.t('mailers.extranet.invitation_messages.manual.signature', locale: language.iso_code) if self.invitation_message_manual_signature.blank? end end diff --git a/app/models/communication/media.rb b/app/models/communication/media.rb index 25850fd7df..0944549a1f 100644 --- a/app/models/communication/media.rb +++ b/app/models/communication/media.rb @@ -3,7 +3,7 @@ # Table name: communication_medias # # id :uuid not null, primary key -# origin :integer default("upload"), not null +# origin :integer default(1), not null # original_byte_size :bigint # original_checksum :string # original_content_type :string diff --git a/app/models/communication/website.rb b/app/models/communication/website.rb index 6189e31370..436e64114f 100644 --- a/app/models/communication/website.rb +++ b/app/models/communication/website.rb @@ -24,9 +24,9 @@ # git_branch :string # git_endpoint :string # git_files_analysed_at :datetime -# git_provider :integer default("github") +# git_provider :integer default(0) # highlighted_in_showcase :boolean default(FALSE) -# hosting :integer default("deuxfleurs"), not null +# hosting :integer default(1), not null # in_production :boolean default(FALSE) # in_production_at :datetime # in_showcase :boolean default(TRUE) diff --git a/app/models/communication/website/alert.rb b/app/models/communication/website/alert.rb index d484925d25..ce01fd4aa8 100644 --- a/app/models/communication/website/alert.rb +++ b/app/models/communication/website/alert.rb @@ -4,7 +4,7 @@ # # id :uuid not null, primary key # deleted_at :datetime -# kind :integer default("info"), not null +# kind :integer default(0), not null # created_at :datetime not null # updated_at :datetime not null # communication_website_id :uuid not null, indexed diff --git a/app/models/communication/website/menu/item.rb b/app/models/communication/website/menu/item.rb index 92b426c86f..f2dd1b7a62 100644 --- a/app/models/communication/website/menu/item.rb +++ b/app/models/communication/website/menu/item.rb @@ -5,7 +5,7 @@ # id :uuid not null, primary key # about_type :string indexed => [about_id] # html_class :string -# kind :integer default("blank") +# kind :integer default(0) # position :integer not null # position_in_tree :integer # should_open_new_tab :boolean default(FALSE) diff --git a/app/models/education/diploma.rb b/app/models/education/diploma.rb index 40eda6a697..3ccbccff34 100644 --- a/app/models/education/diploma.rb +++ b/app/models/education/diploma.rb @@ -6,7 +6,7 @@ # certification :string # deleted_at :datetime # ects :integer -# level :integer default("not_applicable") +# level :integer default(0) # position :integer not null # created_at :datetime not null # updated_at :datetime not null diff --git a/app/models/import.rb b/app/models/import.rb index c4f08546c2..5e8c040f9f 100644 --- a/app/models/import.rb +++ b/app/models/import.rb @@ -6,7 +6,7 @@ # kind :integer # number_of_lines :integer # processing_errors :jsonb -# status :integer default("pending") +# status :integer default(0) # created_at :datetime not null # updated_at :datetime not null # language_id :uuid not null, indexed diff --git a/app/models/research/publication.rb b/app/models/research/publication.rb index d5f4aca37f..11efe7a644 100644 --- a/app/models/research/publication.rb +++ b/app/models/research/publication.rb @@ -20,7 +20,7 @@ # publication_date :date # ref :string # slug :string indexed -# source :integer default("osuny") +# source :integer default(0) # title :string # url :string # created_at :datetime not null diff --git a/app/models/university.rb b/app/models/university.rb index ea77d0a0ad..3502b0d004 100644 --- a/app/models/university.rb +++ b/app/models/university.rb @@ -23,7 +23,7 @@ # sso_cert :text # sso_mapping :jsonb # sso_name_identifier_format :string -# sso_provider :integer default("saml") +# sso_provider :integer default(0) # sso_target_url :string # zipcode :string # created_at :datetime not null diff --git a/app/models/university/organization.rb b/app/models/university/organization.rb index 400d48e52c..0e83b35498 100644 --- a/app/models/university/organization.rb +++ b/app/models/university/organization.rb @@ -9,7 +9,7 @@ # country :string # deleted_at :datetime # email :string -# kind :integer default("company") +# kind :integer default(10) # latitude :float # longitude :float # migration_identifier :string diff --git a/app/models/university/person.rb b/app/models/university/person.rb index 844118e191..897514e283 100644 --- a/app/models/university/person.rb +++ b/app/models/university/person.rb @@ -4,14 +4,14 @@ # # id :uuid not null, primary key # address :string -# address_visibility :integer default("private") +# address_visibility :integer default(0) # birthdate :date # bodyclass :string # city :string # country :string # deleted_at :datetime # email :string -# email_visibility :integer default("private") +# email_visibility :integer default(0) # gender :integer # habilitation :boolean default(FALSE) # invitation_sent_at :datetime @@ -20,16 +20,16 @@ # is_author :boolean # is_researcher :boolean # is_teacher :boolean -# linkedin_visibility :integer default("private") -# mastodon_visibility :integer default("private") +# linkedin_visibility :integer default(0) +# mastodon_visibility :integer default(0) # phone_mobile :string -# phone_mobile_visibility :integer default("private") +# phone_mobile_visibility :integer default(0) # phone_personal :string -# phone_personal_visibility :integer default("private") +# phone_personal_visibility :integer default(0) # phone_professional :string -# phone_professional_visibility :integer default("private") +# phone_professional_visibility :integer default(0) # tenure :boolean default(FALSE) -# twitter_visibility :integer default("private") +# twitter_visibility :integer default(0) # zipcode :string # created_at :datetime not null # updated_at :datetime not null diff --git a/app/models/university/person/alumnus.rb b/app/models/university/person/alumnus.rb index 59a83846dd..ecd01470c1 100644 --- a/app/models/university/person/alumnus.rb +++ b/app/models/university/person/alumnus.rb @@ -4,14 +4,14 @@ # # id :uuid not null, primary key # address :string -# address_visibility :integer default("private") +# address_visibility :integer default(0) # birthdate :date # bodyclass :string # city :string # country :string # deleted_at :datetime # email :string -# email_visibility :integer default("private") +# email_visibility :integer default(0) # gender :integer # habilitation :boolean default(FALSE) # invitation_sent_at :datetime @@ -20,16 +20,16 @@ # is_author :boolean # is_researcher :boolean # is_teacher :boolean -# linkedin_visibility :integer default("private") -# mastodon_visibility :integer default("private") +# linkedin_visibility :integer default(0) +# mastodon_visibility :integer default(0) # phone_mobile :string -# phone_mobile_visibility :integer default("private") +# phone_mobile_visibility :integer default(0) # phone_personal :string -# phone_personal_visibility :integer default("private") +# phone_personal_visibility :integer default(0) # phone_professional :string -# phone_professional_visibility :integer default("private") +# phone_professional_visibility :integer default(0) # tenure :boolean default(FALSE) -# twitter_visibility :integer default("private") +# twitter_visibility :integer default(0) # zipcode :string # created_at :datetime not null # updated_at :datetime not null diff --git a/app/models/university/person/with_alumnus.rb b/app/models/university/person/with_alumnus.rb index 58a4e0fcd8..35fb5a6a36 100644 --- a/app/models/university/person/with_alumnus.rb +++ b/app/models/university/person/with_alumnus.rb @@ -1,6 +1,8 @@ module University::Person::WithAlumnus extend ActiveSupport::Concern + DELAY_FOR_INVITATION = 5.minutes.freeze + included do has_and_belongs_to_many :cohorts, class_name: '::Administration::Cohort', @@ -9,7 +11,7 @@ module University::Person::WithAlumnus accepts_nested_attributes_for :cohorts, reject_if: :all_blank, allow_destroy: true - before_validation :find_cohorts + before_validation :find_cohorts, :sync_diploma_denormalizations validates_associated :cohorts # Dénormalisation des liens via cohorts, pour la recherche par facettes @@ -69,6 +71,10 @@ def for_alumni_account(with_account, extranet) end end + def diploma_years_sentence + diploma_years.map(&:year).compact.uniq.sort.join(', ') + end + def find_cohorts # based on https://stackoverflow.com/questions/3579924/accepts-nested-attributes-for-with-find-or-create cohorts_to_set = [] @@ -83,8 +89,21 @@ def find_cohorts self.cohorts = cohorts_to_set end + # Ajoute une cohorte en gardant les dénormalisations à jour + def add_cohort(cohort) + return if cohort.in?(cohorts) + cohorts << cohort + sync_diploma_denormalizations + end + private + # Dénormalisation des liens via cohorts, pour la recherche par facettes + def sync_diploma_denormalizations + self.diploma_years = cohorts.map(&:academic_year).compact.uniq + self.diploma_programs = cohorts.map(&:program).compact.uniq + end + def find_cohort_for_nested(object) academic_year = Administration::AcademicYear.where(university_id: university_id, year: object.year).first_or_create cohort = Administration::Cohort.where(university_id: university_id, school_id: object.school_id, program_id: object.program_id, academic_year_id: academic_year.id).first_or_initialize diff --git a/app/models/user.rb b/app/models/user.rb index 2bd11ecec5..f0afb71c9f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -28,7 +28,7 @@ # remember_created_at :datetime # reset_password_sent_at :datetime # reset_password_token :string uniquely indexed -# role :integer default("visitor") +# role :integer default(0) # second_factor_attempts_count :integer default(0) # session_token :string # sign_in_count :integer default(0), not null diff --git a/app/models/user/with_person.rb b/app/models/user/with_person.rb index 7b3bd544f8..e4188141a3 100644 --- a/app/models/user/with_person.rb +++ b/app/models/user/with_person.rb @@ -5,6 +5,9 @@ module User::WithPerson # Original person has_one :person, class_name: 'University::Person', dependent: :nullify + # Set during registration through an extranet invitation + attr_accessor :invitation + delegate :experiences, to: :person after_save_commit :sync_person, if: :person @@ -28,9 +31,10 @@ def sync_person_safely protected def find_or_create_person - person = university.people.where(email: email).first_or_initialize + person = invitation&.person || university.people.where(email: email).first_or_initialize person_l10n = person.localizations.find_by(language_id: university.default_language_id) person.user = self + person.email = email person.localizations_attributes = [ { id: person_l10n&.id, language_id: university.default_language_id, diff --git a/app/models/user/with_registration_context.rb b/app/models/user/with_registration_context.rb index eaa45ad7ac..73a53b5ca3 100644 --- a/app/models/user/with_registration_context.rb +++ b/app/models/user/with_registration_context.rb @@ -32,7 +32,7 @@ def extranet_access end def user_can_access_registration_context? - user_is_alumni? || user_is_contact? + user_is_alumni? || user_is_contact? || user_is_invited? end def user_is_alumni? @@ -43,6 +43,10 @@ def user_is_contact? registration_context.has_feature?(:contacts) && registration_context.connected_people.where(email: email).any? end + def user_is_invited? + invitation.present? && invitation.extranet_id == registration_context.id + end + def send_notification_to_admins return if server_admin? # ignore server admins to prevent spam during account replication wetween universities GroupNotificationMailer.new_registration(university, self).deliver_later diff --git a/app/services/importers/alumni_cohorts.rb b/app/services/importers/alumni_cohorts.rb index 1b5b8556dc..adbb23b325 100644 --- a/app/services/importers/alumni_cohorts.rb +++ b/app/services/importers/alumni_cohorts.rb @@ -36,7 +36,7 @@ def send_extranet_invitation_emails @connections.uniq.each do |connection| extranet = @university.communication_extranets.find(connection[:communication_extranet_id]) person = @university.people.find(connection[:person_id]) - ExtranetMailer.invitation_message(extranet, person).deliver_later + ExtranetMailer.invitation_message_automatic(extranet, person).deliver_later person.update_column(:invitation_sent_at, Time.current) end end diff --git a/app/services/importers/hash_to_cohort.rb b/app/services/importers/hash_to_cohort.rb index 7e428bee53..633f82fa43 100644 --- a/app/services/importers/hash_to_cohort.rb +++ b/app/services/importers/hash_to_cohort.rb @@ -36,13 +36,7 @@ def extranet_ids protected def add_to_cohort(person, cohort) - add_object_if_necessary cohort, person.cohorts - add_object_if_necessary cohort.academic_year, person.diploma_years - add_object_if_necessary cohort.program, person.diploma_programs - end - - def add_object_if_necessary(object, list) - list << object unless object.in?(list) + person.add_cohort(cohort) end def extract_variables diff --git a/app/views/admin/communication/extranets/_form.html.erb b/app/views/admin/communication/extranets/_form.html.erb index a5fc6dac30..104ca69f07 100644 --- a/app/views/admin/communication/extranets/_form.html.erb +++ b/app/views/admin/communication/extranets/_form.html.erb @@ -72,9 +72,37 @@ <%= f.input :feature_jobs %> <% end %> <% end %> - <%= osuny_panel Communication::Extranet::Localization.human_attribute_name(:invitation_message) do %> - <%= lf.input :invitation_message_subject %> - <%= lf.input :invitation_message_text, input_html: { rows: 5 } %> + <%= osuny_panel t('admin.communication.extranet.invitation_messages.title') do %> +

<%= t('admin.communication.extranet.invitation_messages.automatic') %>

+

<%= t('admin.communication.extranet.invitation_messages.automatic_hint') %>

+ <%= lf.input :invitation_message_automatic_subject, + input_html: { + value: l10n.invitation_message_automatic_subject.presence || t('mailers.extranet.invitation_messages.automatic.subject') + } %> + <%= lf.input :invitation_message_automatic_text, + as: :text, + input_html: { + rows: 5, + value: l10n.invitation_message_automatic_text.presence || t('mailers.extranet.invitation_messages.automatic.text') + } %> +

<%= t('admin.communication.extranet.invitation_messages.manual') %>

+

<%= t('admin.communication.extranet.invitation_messages.manual_hint_html') %>

+ <%= lf.input :invitation_message_manual_subject, + input_html: { + value: l10n.invitation_message_manual_subject.presence || t('mailers.extranet.invitation_messages.manual.subject') + } %> + <%= lf.input :invitation_message_manual_text, + as: :text, + input_html: { + rows: 5, + value: l10n.invitation_message_manual_text.presence || t('mailers.extranet.invitation_messages.manual.text') + } %> + <%= lf.input :invitation_message_manual_signature, + as: :text, + input_html: { + rows: 2, + value: l10n.invitation_message_manual_signature.presence || t('mailers.extranet.invitation_messages.manual.signature') + } %> <% end %> <%= osuny_panel t('legal') do %> <%= lf.input :terms, as: :summernote %> diff --git a/app/views/admin/communication/extranets/alumni/_list.html.erb b/app/views/admin/communication/extranets/alumni/_list.html.erb index 06563f5752..ae211844b2 100644 --- a/app/views/admin/communication/extranets/alumni/_list.html.erb +++ b/app/views/admin/communication/extranets/alumni/_list.html.erb @@ -15,7 +15,7 @@

<%= t('admin.communication.extranet.alumni.account_active') %>

- <% elsif person.invitation_sent_at.nil? || person.invitation_sent_at < 5.minutes.ago %> + <% elsif person.invitation_sent_at.nil? || person.invitation_sent_at < University::Person::WithAlumnus::DELAY_FOR_INVITATION.ago %> <%= link_to t('admin.communication.extranet.alumni.send_invitation.cta'), send_invitation_admin_communication_extranet_alumnus_path(extranet_id: @extranet.id, id: person.id), method: :post, diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb index 575d78d982..4529950b17 100644 --- a/app/views/devise/registrations/new.html.erb +++ b/app/views/devise/registrations/new.html.erb @@ -6,6 +6,8 @@ <%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> <%= f.error_notification %> + <%= hidden_field_tag :invitation_token, params[:invitation_token] if params[:invitation_token].present? %> +
<%= f.invisible_captcha :osuny_verification %> diff --git a/app/views/extranet/alumni/persons/_person.html.erb b/app/views/extranet/alumni/persons/_person.html.erb index 1fc23889a0..e6cf8632de 100644 --- a/app/views/extranet/alumni/persons/_person.html.erb +++ b/app/views/extranet/alumni/persons/_person.html.erb @@ -1,5 +1,5 @@ <% -l10n = person.best_localization_for(person) +l10n = person.best_localization_for(current_language) %>
@@ -16,5 +16,6 @@ l10n = person.best_localization_for(person)
<%= link_to l10n, [:alumni, person], class: 'stretched-link' %> + <%= render 'extranet/alumni/persons/invitations/cta', person: person, l10n: l10n %>
diff --git a/app/views/extranet/alumni/persons/invitations/_cta.html.erb b/app/views/extranet/alumni/persons/invitations/_cta.html.erb new file mode 100644 index 0000000000..235aab7b98 --- /dev/null +++ b/app/views/extranet/alumni/persons/invitations/_cta.html.erb @@ -0,0 +1,13 @@ +<% if person.user_id.blank? && person.email.blank? %> +

+ <%= t('extranet.alumni.invitations.send.label') %> +
+ <% if Communication::Extranet::Invitation.sendable_to?(person) %> + <%= link_to t('extranet.alumni.invitations.send.cta', name: l10n), + alumni_new_invitation_path(person), + class: 'btn btn-light btn-sm position-relative z-2' %> + <% else %> + <%= t('extranet.alumni.invitations.send.too_soon') %> + <% end %> +

+<% end %> \ No newline at end of file diff --git a/app/views/extranet/alumni/persons/invitations/new.html.erb b/app/views/extranet/alumni/persons/invitations/new.html.erb new file mode 100644 index 0000000000..abd4f9e4c7 --- /dev/null +++ b/app/views/extranet/alumni/persons/invitations/new.html.erb @@ -0,0 +1,22 @@ +<% content_for :title, t('extranet.alumni.invitations.title') %> + +<%= simple_form_for @invitation, url: alumni_new_invitation_path(@person) do |f| %> + <%= f.error_notification %> + <%= f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present? %> + +
+
+ <%= f.input :from_name %> + <%= f.input :from_email %> + <%= f.input :to_name %> + <%= f.input :to_email %> +
+
+ <%= f.input :message, input_html: { rows: 10 } %> +
+
+ + <%= submit f %> + +<% end %> + diff --git a/app/views/extranet/alumni/persons/show.html.erb b/app/views/extranet/alumni/persons/show.html.erb index 7a86b52dbe..458c8746f8 100644 --- a/app/views/extranet/alumni/persons/show.html.erb +++ b/app/views/extranet/alumni/persons/show.html.erb @@ -1,5 +1,7 @@ <% content_for :title, @l10n %> +<%= render 'extranet/alumni/persons/invitations/cta', person: @person, l10n: @l10n %> +
diff --git a/app/views/extranet/home/features/_alumni.html.erb b/app/views/extranet/home/features/_alumni.html.erb index c409a16f84..ece21e87e5 100644 --- a/app/views/extranet/home/features/_alumni.html.erb +++ b/app/views/extranet/home/features/_alumni.html.erb @@ -6,6 +6,7 @@
    <% @experiences.ordered.each do |experience| %> <% l10n = experience.localization_for(current_language) %> + <% person_l10n = experience.person.best_localization_for(current_language) %> <% organization_l10n = experience.organization.best_localization_for(current_language) if experience.organization.present? %>
  • @@ -23,14 +24,15 @@

    - <%= experience.person.best_localization_for(current_language) %> + <%= person_l10n %>
    <%= [l10n&.description, organization_l10n&.to_s].compact_blank.join('-') %>

    + <%= render 'extranet/alumni/persons/invitations/cta', person: experience.person, l10n: person_l10n %> <%= l experience.created_at, format: :date_with_explicit_month %>
    <% if organization_l10n && organization_l10n.logo.attached? %> - <%= link_to [:alumni, experience.organization] do %> + <%= link_to [:alumni, experience.organization], class: 'position-relative z-2' do %> <%= kamifusen_tag organization_l10n.logo, height: 80, class: 'img-fluid' %> <% end %> <% end %> diff --git a/app/views/mailers/extranet/invitation_message.html.erb b/app/views/mailers/extranet/invitation_message_automatic.html.erb similarity index 60% rename from app/views/mailers/extranet/invitation_message.html.erb rename to app/views/mailers/extranet/invitation_message_automatic.html.erb index 2aa5b94f5d..49517198a3 100644 --- a/app/views/mailers/extranet/invitation_message.html.erb +++ b/app/views/mailers/extranet/invitation_message_automatic.html.erb @@ -1,14 +1,14 @@ -<%= simple_format @l10n.invitation_message_text %> +<%= simple_format @l10n.invitation_message_automatic_text %> <% if @user.present? %>

    - <%= t('mailers.extranet.invitation_message.with_user_html', + <%= t('mailers.extranet.invitation_messages.automatic.with_user_html', email: @email, sign_in_url: new_user_session_url(email: @email, host: @extranet.host)) %>

    <% else %>

    - <%= t('mailers.extranet.invitation_message.without_user_html', + <%= t('mailers.extranet.invitation_messages.automatic.without_user_html', email: @email, sign_up_url: new_user_registration_url(email: @email, host: @extranet.host)) %>

    diff --git a/app/views/mailers/extranet/invitation_message_manual.html.erb b/app/views/mailers/extranet/invitation_message_manual.html.erb new file mode 100644 index 0000000000..67b761d221 --- /dev/null +++ b/app/views/mailers/extranet/invitation_message_manual.html.erb @@ -0,0 +1,7 @@ +<%= simple_format @invitation.message %> + +

    + <%= link_to t('mailers.extranet.invitation_messages.manual.cta'), @registration_url, target: '_blank' %> +

    + +<%= simple_format @signature %> diff --git a/config/locales/communication/en.yml b/config/locales/communication/en.yml index 854a535f40..2bd89a4c3e 100644 --- a/config/locales/communication/en.yml +++ b/config/locales/communication/en.yml @@ -56,13 +56,21 @@ en: name: Name published: Published? published_at: Publication date + communication/extranet/invitation: + from_email: Your email + from_name: From + message: Message + to_email: Alumnus' email + to_name: To communication/extranet/localization: cookies_policy: Cookies policy favicon: Browser icon (favicon) home_sentence: Sentence displayed on homepage - invitation_message: Invitation message (automatically sended) - invitation_message_subject: Subject - invitation_message_text: Texte + invitation_message_automatic_subject: Subject + invitation_message_automatic_text: Text + invitation_message_manual_signature: Signature + invitation_message_manual_subject: Subject + invitation_message_manual_text: Text logo: Logo name: Name privacy_policy: Privacy policy @@ -337,6 +345,10 @@ en: unavailable: is not available for this kind of extranet sso_mapping: missing_email: doesn't handle the email + communication/extranet/invitation: + attributes: + to_email: + too_soon: has already received an invitation recently. You can try again later. communication/extranet/localization: attributes: published: @@ -520,6 +532,12 @@ en: confirm_localization: text_html: "The creation of %{about} in %{language} requires the localization of the extranet %{extranet} in %{language}." title: Localization confirmation + invitation_messages: + automatic: Automatic message + automatic_hint: This message will be sent after an alumni import or when clicking on the "send invitation" button on admin, if the alumnus has an email. + manual: Manual message + manual_hint_html: "This message will be sent from the extranet, when a user clicks on the \"invite alumnus\" button, if the user has no email.
    In these fields you can use the following variables:
    • {{name}}: the name of the alumnus
    • {{sender_name}}: the name of the user sending the invitation
    • {{sender_years}}: the promotion(s) of the user sending the invitation
    " + title: Invitation messages languages: Languages website: agenda: diff --git a/config/locales/communication/fr.yml b/config/locales/communication/fr.yml index acacda3553..f4a78ca6e4 100644 --- a/config/locales/communication/fr.yml +++ b/config/locales/communication/fr.yml @@ -56,13 +56,21 @@ fr: name: Nom published: Publié ? published_at: Date de publication + communication/extranet/invitation: + from_email: Votre email + from_name: De la part de + message: Message + to_email: Email de la personne invitée + to_name: Pour communication/extranet/localization: cookies_policy: Politique de cookies favicon: Icône de navigateur (favicon) home_sentence: Phrase affichée sur la home - invitation_message: Message d'invitation automatique - invitation_message_subject: Sujet du mail - invitation_message_text: Texte du mail + invitation_message_automatic_subject: Sujet du mail + invitation_message_automatic_text: Texte du mail + invitation_message_manual_signature: Signature + invitation_message_manual_subject: Sujet du mail + invitation_message_manual_text: Texte du mail logo: Logo name: Nom privacy_policy: Politique de confidentialité @@ -337,6 +345,10 @@ fr: unavailable: n'est pas disponible pour ce type d'extranet sso_mapping: missing_email: ne gère pas l'adresse email + communication/extranet/invitation: + attributes: + to_email: + too_soon: a déjà reçu une invitation récemment. Vous pouvez réessayer plus tard. communication/extranet/localization: attributes: published: @@ -520,6 +532,12 @@ fr: confirm_localization: text_html: "La création de %{about} en %{language} nécessite la traduction de l'extranet %{extranet} en %{language}." title: Confirmation de traduction + invitation_messages: + automatic: Message automatique + automatic_hint: Ce message sera envoyé après un import d'alumni ou lors du clic sur le bouton "envoyer l'invitation" dans l'admin, si l'alumnus a un email. + manual: Message manuel + manual_hint_html: "Ce message sera envoyé depuis l'extranet, lorsqu'un utilisateur clique sur le bouton \"inviter un alumnus\", si l'utilisateur n'a pas d'email.
    Dans ces champs vous pouvez utiliser les variables suivantes :
    • {{name}} : le nom de l'alumnus
    • {{sender_name}} : le nom de l'utilisateur qui envoie l'invitation
    • {{sender_years}} : la/les promotion(s) de l'utilisateur qui envoie l'invitation
    " + title: Messages d'invitation languages: Langues website: agenda: diff --git a/config/locales/en.yml b/config/locales/en.yml index 9b89adae69..acbc25788e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -497,11 +497,25 @@ en: look_feel: Look & feel mailers: extranet: - invitation_message: - subject: Welcome to your extranet - text: You now have access to the extranet. - with_user_html: You can sign in with your email address %{email} by clicking here. - without_user_html: You can sign up with your email address %{email} by clicking here. + invitation_messages: + automatic: + subject: Welcome to your extranet + text: You now have access to the extranet. + with_user_html: You can sign in with your email address %{email} by clicking here. + without_user_html: You can sign up with your email address %{email} by clicking here. + manual: + cta: Join the extranet + signature: Yours + subject: Invitation from {{sender_name}} (promotion {{sender_years}}) to join the extranet + text: | + Hello {{name}}, + + {{sender_name}} invites you to join the Alumni extranet. + + You will find, in particular, the directory of all promotions. + See you soon! + + {{sender_name}} (promotion {{sender_years}})" notifications: gdpr_deletion_incoming: subject: "osuny - your account will soon be deleted" diff --git a/config/locales/extranet/en.yml b/config/locales/extranet/en.yml index ac38c94a72..0ca99960f6 100644 --- a/config/locales/extranet/en.yml +++ b/config/locales/extranet/en.yml @@ -8,6 +8,14 @@ en: logout: Log out my: My account updated: Updated + alumni: + invitations: + created: Invitation sent successfully + send: + cta: Invite %{name} to join the alumni + label: Do you know his/her email? You can invite him/her. + too_soon: An invitation has already been sent to this person recently. You can try again later. + title: Invite contacts: organizations: people: Members of this organization diff --git a/config/locales/extranet/fr.yml b/config/locales/extranet/fr.yml index 2ac47e60dd..d39cf876e3 100644 --- a/config/locales/extranet/fr.yml +++ b/config/locales/extranet/fr.yml @@ -8,6 +8,14 @@ fr: logout: Déconnexion my: Mon compte updated: Mise à jour effectuée + alumni: + invitations: + created: Invitation envoyée avec succès + send: + cta: Inviter %{name} à rejoindre les alumni + label: Vous connaissez son email ? Vous pouvez l’inviter. + too_soon: Une invitation a déjà été envoyée à cette personne récemment. Vous pouvez réessayer plus tard. + title: Inviter contacts: organizations: people: Membres de cette organisation diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 5d6d02f4aa..fe1c97c2e1 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -498,11 +498,25 @@ fr: look_feel: Look & feel mailers: extranet: - invitation_message: - subject: Bienvenue sur votre extranet - text: Vous avez désormais accès à l'extranet. - with_user_html: Vous pouvez vous y connecter avec votre adresse email %{email} en cliquant ici. - without_user_html: Vous pouvez vous y inscrire avec votre adresse email %{email} en cliquant ici. + invitation_messages: + automatic: + subject: Bienvenue sur votre extranet + text: Vous avez désormais accès à l'extranet. + with_user_html: Vous pouvez vous y connecter avec votre adresse email %{email} en cliquant ici. + without_user_html: Vous pouvez vous y inscrire avec votre adresse email %{email} en cliquant ici. + manual: + cta: Rejoindre l'extranet + signature: Cordialement + subject: Invitation de {{sender_name}} (promotion {{sender_years}}) à rejoindre l'extranet + text: | + Bonjour {{name}}, + + {{sender_name}} vous invite à rejoindre l'extranet des Alumni. + + Vous y retrouverez notamment l'annuaire de toutes les promotions. + À très bientôt ! + + {{sender_name}} (promotion {{sender_years}}) notifications: gdpr_deletion_incoming: subject: "osuny - votre compte va bientôt être supprimé" diff --git a/config/routes/extranet.rb b/config/routes/extranet.rb index fdc2e568d3..0b00f56986 100644 --- a/config/routes/extranet.rb +++ b/config/routes/extranet.rb @@ -18,6 +18,8 @@ get 'organization/:id' => 'organizations#show', as: :university_organization get 'persons' => 'persons#index', as: :university_persons get 'persons/:id' => 'persons#show', as: :university_person + get 'persons/:id/invite' => 'persons/invitations#new', as: :new_invitation + post 'persons/:id/invite' => 'persons/invitations#create' get 'years' => 'academic_years#index', as: :administration_academic_years get 'years/:id' => 'academic_years#show', as: :administration_academic_year root to: 'persons#index' diff --git a/db/migrate/20260706155316_create_communication_extranet_invitations.rb b/db/migrate/20260706155316_create_communication_extranet_invitations.rb new file mode 100644 index 0000000000..4d606e4269 --- /dev/null +++ b/db/migrate/20260706155316_create_communication_extranet_invitations.rb @@ -0,0 +1,16 @@ +class CreateCommunicationExtranetInvitations < ActiveRecord::Migration[8.1] + def change + create_table :communication_extranet_invitations, id: :uuid do |t| + t.references :extranet, foreign_key: { to_table: :communication_extranets }, type: :uuid + t.references :user, foreign_key: true, type: :uuid + t.references :person, foreign_key: { to_table: :university_people }, type: :uuid + t.references :university, foreign_key: true, type: :uuid + t.string :from_name + t.string :from_email + t.string :to_name + t.string :to_email + t.text :message + t.timestamps + end + end +end diff --git a/db/migrate/20260716132522_add_manual_invitation_message_infos_to_extranets.rb b/db/migrate/20260716132522_add_manual_invitation_message_infos_to_extranets.rb new file mode 100644 index 0000000000..eb1e3c1497 --- /dev/null +++ b/db/migrate/20260716132522_add_manual_invitation_message_infos_to_extranets.rb @@ -0,0 +1,9 @@ +class AddManualInvitationMessageInfosToExtranets < ActiveRecord::Migration[8.1] + def change + rename_column :communication_extranet_localizations, :invitation_message_subject, :invitation_message_automatic_subject + rename_column :communication_extranet_localizations, :invitation_message_text, :invitation_message_automatic_text + add_column :communication_extranet_localizations, :invitation_message_manual_subject, :string + add_column :communication_extranet_localizations, :invitation_message_manual_text, :text + add_column :communication_extranet_localizations, :invitation_message_manual_signature, :text + end +end diff --git a/db/migrate/20260806085855_add_token_to_communication_extranet_invitations.rb b/db/migrate/20260806085855_add_token_to_communication_extranet_invitations.rb new file mode 100644 index 0000000000..9fe69d81b0 --- /dev/null +++ b/db/migrate/20260806085855_add_token_to_communication_extranet_invitations.rb @@ -0,0 +1,6 @@ +class AddTokenToCommunicationExtranetInvitations < ActiveRecord::Migration[8.1] + def change + add_column :communication_extranet_invitations, :token, :string + add_index :communication_extranet_invitations, :token, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index ec82aa93c8..54dcfc2258 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2026_07_02_133651) do +ActiveRecord::Schema[8.1].define(version: 2026_08_06_085855) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" enable_extension "pg_stat_statements" @@ -18,7 +18,7 @@ enable_extension "pgcrypto" enable_extension "unaccent" - create_table "action_text_rich_texts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "action_text_rich_texts", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.text "body" t.datetime "created_at", null: false t.string "name", null: false @@ -45,7 +45,7 @@ t.index ["ip_address", "created_at"], name: "index_active_hashcash_stamps_on_ip_address_and_created_at", where: "(ip_address IS NOT NULL)" end - create_table "active_storage_attachments", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "active_storage_attachments", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.uuid "blob_id", null: false t.datetime "created_at", precision: nil, null: false t.datetime "deleted_at" @@ -56,7 +56,7 @@ t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true end - create_table "active_storage_blobs", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "active_storage_blobs", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.bigint "byte_size", null: false t.string "checksum" t.string "content_type" @@ -70,7 +70,7 @@ t.index ["university_id"], name: "index_active_storage_blobs_on_university_id" end - create_table "active_storage_variant_records", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "active_storage_variant_records", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.uuid "blob_id", null: false t.string "variation_digest", null: false t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true @@ -90,7 +90,7 @@ t.index ["university_id"], name: "idx_on_university_id_31eabbc7a7" end - create_table "administration_academic_years", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "administration_academic_years", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.datetime "created_at", null: false t.datetime "deleted_at" t.uuid "university_id", null: false @@ -120,7 +120,7 @@ t.index ["university_id"], name: "index_administration_cohort_localizations_on_university_id" end - create_table "administration_cohorts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "administration_cohorts", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.uuid "academic_year_id", null: false t.datetime "created_at", null: false t.datetime "deleted_at" @@ -193,7 +193,7 @@ t.index ["education_school_id", "administration_location_id"], name: "index_location_school" end - create_table "administration_qualiopi_criterions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "administration_qualiopi_criterions", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.datetime "created_at", null: false t.text "description" t.text "name" @@ -201,7 +201,7 @@ t.datetime "updated_at", null: false end - create_table "administration_qualiopi_indicators", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "administration_qualiopi_indicators", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.datetime "created_at", null: false t.uuid "criterion_id", null: false t.text "glossary" @@ -215,7 +215,7 @@ t.index ["criterion_id"], name: "index_administration_qualiopi_indicators_on_criterion_id" end - create_table "communication_blocks", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "communication_blocks", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.uuid "about_id" t.string "about_type" t.uuid "communication_website_id" @@ -236,7 +236,7 @@ t.index ["university_id", "template_kind"], name: "index_communication_blocks_on_university_id_and_template_kind" end - create_table "communication_extranet_connections", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "communication_extranet_connections", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.uuid "about_id" t.string "about_type" t.datetime "created_at", null: false @@ -248,7 +248,7 @@ t.index ["university_id"], name: "index_communication_extranet_connections_on_university_id" end - create_table "communication_extranet_document_categories", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "communication_extranet_document_categories", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.datetime "created_at", null: false t.uuid "extranet_id", null: false t.uuid "university_id", null: false @@ -289,7 +289,7 @@ t.index ["university_id"], name: "idx_on_university_id_0dc1259072" end - create_table "communication_extranet_document_kinds", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "communication_extranet_document_kinds", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.datetime "created_at", null: false t.uuid "extranet_id", null: false t.uuid "university_id", null: false @@ -315,7 +315,7 @@ t.index ["university_id"], name: "idx_on_university_id_95419f1df4" end - create_table "communication_extranet_documents", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "communication_extranet_documents", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.uuid "category_id" t.datetime "created_at", null: false t.uuid "extranet_id", null: false @@ -328,13 +328,36 @@ t.index ["university_id"], name: "index_communication_extranet_documents_on_university_id" end + create_table "communication_extranet_invitations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.datetime "created_at", null: false + t.uuid "extranet_id" + t.string "from_email" + t.string "from_name" + t.text "message" + t.uuid "person_id" + t.string "to_email" + t.string "to_name" + t.string "token" + t.uuid "university_id" + t.datetime "updated_at", null: false + t.uuid "user_id" + t.index ["extranet_id"], name: "index_communication_extranet_invitations_on_extranet_id" + t.index ["person_id"], name: "index_communication_extranet_invitations_on_person_id" + t.index ["token"], name: "index_communication_extranet_invitations_on_token", unique: true + t.index ["university_id"], name: "index_communication_extranet_invitations_on_university_id" + t.index ["user_id"], name: "index_communication_extranet_invitations_on_user_id" + end + create_table "communication_extranet_localizations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.uuid "about_id" t.text "cookies_policy" t.datetime "created_at", null: false t.text "home_sentence" - t.string "invitation_message_subject", default: "" - t.text "invitation_message_text", default: "" + t.string "invitation_message_automatic_subject", default: "" + t.text "invitation_message_automatic_text", default: "" + t.text "invitation_message_manual_signature" + t.string "invitation_message_manual_subject" + t.text "invitation_message_manual_text" t.uuid "language_id" t.string "name" t.text "privacy_policy" @@ -351,7 +374,7 @@ t.index ["university_id"], name: "index_communication_extranet_localizations_on_university_id" end - create_table "communication_extranet_post_categories", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "communication_extranet_post_categories", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.datetime "created_at", null: false t.uuid "extranet_id", null: false t.uuid "university_id", null: false @@ -398,7 +421,7 @@ t.index ["university_id"], name: "idx_on_university_id_28188e2217" end - create_table "communication_extranet_posts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "communication_extranet_posts", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.uuid "author_id" t.uuid "category_id" t.datetime "created_at", null: false @@ -411,7 +434,7 @@ t.index ["university_id"], name: "index_communication_extranet_posts_on_university_id" end - create_table "communication_extranets", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "communication_extranets", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.uuid "about_id" t.string "about_type" t.string "color" @@ -766,7 +789,7 @@ t.index ["university_id"], name: "idx_on_university_id_bca328e63c" end - create_table "communication_website_agenda_events", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "communication_website_agenda_events", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.string "bodyclass" t.uuid "communication_website_id", null: false t.datetime "created_at", null: false @@ -957,7 +980,7 @@ t.index ["university_id"], name: "index_communication_website_alerts_on_university_id" end - create_table "communication_website_connections", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "communication_website_connections", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.datetime "created_at", null: false t.uuid "direct_source_id" t.string "direct_source_type" @@ -1013,7 +1036,7 @@ t.index ["university_id"], name: "index_communication_website_git_file_orphans_on_university_id" end - create_table "communication_website_git_files", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "communication_website_git_files", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.uuid "about_id" t.string "about_type" t.datetime "created_at", null: false @@ -1159,7 +1182,7 @@ t.index ["university_id"], name: "index_communication_website_localizations_on_university_id" end - create_table "communication_website_menu_items", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "communication_website_menu_items", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.uuid "about_id" t.string "about_type" t.datetime "created_at", null: false @@ -1182,7 +1205,7 @@ t.index ["website_id"], name: "index_communication_website_menu_items_on_website_id" end - create_table "communication_website_menus", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "communication_website_menus", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.boolean "automatic", default: true t.uuid "communication_website_id", null: false t.datetime "created_at", null: false @@ -1281,7 +1304,7 @@ t.index ["university_id"], name: "idx_on_university_id_e62b2aba53" end - create_table "communication_website_pages", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "communication_website_pages", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.string "bodyclass" t.uuid "communication_website_id", null: false t.datetime "created_at", null: false @@ -1302,7 +1325,7 @@ t.index ["university_id"], name: "index_communication_website_pages_on_university_id" end - create_table "communication_website_permalinks", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "communication_website_permalinks", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.uuid "about_id" t.string "about_type" t.datetime "created_at", null: false @@ -1415,7 +1438,7 @@ t.index ["university_id"], name: "idx_on_university_id_ac2f4a0bfc" end - create_table "communication_website_post_categories", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "communication_website_post_categories", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.string "bodyclass" t.uuid "communication_website_id", null: false t.datetime "created_at", null: false @@ -1503,7 +1526,7 @@ t.index ["unpublication_job_id"], name: "idx_on_unpublication_job_id" end - create_table "communication_website_posts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "communication_website_posts", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.string "bodyclass" t.uuid "communication_website_id", null: false t.datetime "created_at", null: false @@ -1538,7 +1561,7 @@ t.index ["communication_website_showcase_tag_id", "communication_website_id"], name: "index_showcase_tag_website" end - create_table "communication_websites", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "communication_websites", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.uuid "about_id" t.string "about_type" t.string "access_token" @@ -1629,7 +1652,7 @@ t.index ["university_id"], name: "index_education_diploma_localizations_on_university_id" end - create_table "education_diplomas", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "education_diplomas", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.string "certification" t.datetime "created_at", null: false t.datetime "deleted_at" @@ -1728,7 +1751,7 @@ t.index ["university_id"], name: "index_education_program_localizations_on_university_id" end - create_table "education_programs", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "education_programs", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.boolean "apprenticeship" t.string "bodyclass" t.integer "capacity" @@ -1782,7 +1805,7 @@ t.index ["university_id"], name: "index_education_school_localizations_on_university_id" end - create_table "education_schools", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "education_schools", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.string "address" t.string "city" t.string "country" @@ -1797,7 +1820,7 @@ t.index ["university_id"], name: "index_education_schools_on_university_id" end - create_table "emergency_messages", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "emergency_messages", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.text "content_en" t.text "content_fr" t.datetime "created_at", null: false @@ -1903,7 +1926,7 @@ t.index ["scheduled_at"], name: "index_good_jobs_on_scheduled_at", where: "(finished_at IS NULL)" end - create_table "imports", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "imports", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.datetime "created_at", null: false t.integer "kind" t.uuid "language_id", null: false @@ -1918,7 +1941,7 @@ t.index ["user_id"], name: "index_imports_on_user_id" end - create_table "languages", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "languages", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.datetime "created_at", null: false t.string "iso_code" t.string "name" @@ -1932,7 +1955,7 @@ t.index ["university_id", "language_id"], name: "index_languages_universities_on_university_id_and_language_id" end - create_table "research_hal_authors", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "research_hal_authors", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.datetime "created_at", null: false t.string "docid" t.string "first_name" @@ -1991,7 +2014,7 @@ t.index ["university_id"], name: "idx_on_university_id_dc9f1267b7" end - create_table "research_journal_paper_kinds", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "research_journal_paper_kinds", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.datetime "created_at", null: false t.datetime "deleted_at" t.uuid "journal_id", null: false @@ -2023,7 +2046,7 @@ t.index ["university_id"], name: "index_research_journal_paper_localizations_on_university_id" end - create_table "research_journal_papers", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "research_journal_papers", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.date "accepted_at" t.text "bibliography" t.datetime "created_at", null: false @@ -2075,7 +2098,7 @@ t.index ["university_id"], name: "index_research_journal_volume_localizations_on_university_id" end - create_table "research_journal_volumes", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "research_journal_volumes", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.datetime "created_at", null: false t.datetime "deleted_at" t.integer "number" @@ -2086,7 +2109,7 @@ t.index ["university_id"], name: "index_research_journal_volumes_on_university_id" end - create_table "research_journals", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "research_journals", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.datetime "created_at", null: false t.datetime "deleted_at" t.uuid "university_id", null: false @@ -2094,7 +2117,7 @@ t.index ["university_id"], name: "index_research_journals_on_university_id" end - create_table "research_laboratories", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "research_laboratories", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.string "address" t.string "city" t.string "country" @@ -2113,7 +2136,7 @@ t.index ["university_person_id", "research_laboratory_id"], name: "laboratory_person" end - create_table "research_laboratory_axes", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "research_laboratory_axes", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.datetime "created_at", null: false t.datetime "deleted_at" t.integer "position", null: false @@ -2158,7 +2181,7 @@ t.index ["university_id"], name: "index_research_laboratory_localizations_on_university_id" end - create_table "research_publications", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "research_publications", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.text "abstract" t.text "anr_project_references", default: [], array: true t.json "authors_citeproc" @@ -2192,7 +2215,7 @@ t.index ["university_person_id", "research_publication_id"], name: "index_publication_person" end - create_table "research_theses", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "research_theses", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.uuid "author_id", null: false t.boolean "completed", default: false t.date "completed_at" @@ -2251,7 +2274,7 @@ t.datetime "updated_at", null: false end - create_table "universities", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "universities", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.string "address" t.boolean "admin_already_auto_promoted", default: false t.string "city" @@ -2282,7 +2305,7 @@ t.index ["name"], name: "index_universities_on_name", opclass: :gin_trgm_ops, using: :gin end - create_table "university_apps", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "university_apps", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.datetime "created_at", null: false t.string "name" t.string "token" @@ -2293,7 +2316,7 @@ t.index ["university_id"], name: "index_university_apps_on_university_id" end - create_table "university_organization_categories", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "university_organization_categories", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.string "bodyclass" t.datetime "created_at", null: false t.boolean "is_taxonomy", default: false @@ -2307,7 +2330,7 @@ t.index ["university_id"], name: "index_university_organization_categories_on_university_id" end - create_table "university_organization_categories_organizations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "university_organization_categories_organizations", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.uuid "category_id", null: false t.uuid "organization_id", null: false t.index ["category_id"], name: "idx_on_category_id_7494b991ff" @@ -2370,7 +2393,7 @@ t.index ["university_id"], name: "index_university_organization_localizations_on_university_id" end - create_table "university_organizations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "university_organizations", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.string "address" t.string "bodyclass" t.string "city" @@ -2393,7 +2416,7 @@ t.index ["university_id"], name: "index_university_organizations_on_university_id" end - create_table "university_people", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "university_people", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.string "address" t.integer "address_visibility", default: 0 t.date "birthdate" @@ -2432,14 +2455,14 @@ t.index ["user_id"], name: "index_university_people_on_user_id" end - create_table "university_people_person_categories", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "university_people_person_categories", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.uuid "category_id", null: false t.uuid "person_id", null: false t.index ["category_id"], name: "index_university_people_person_categories_on_category_id" t.index ["person_id"], name: "index_university_people_person_categories_on_person_id" end - create_table "university_person_categories", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "university_person_categories", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.string "bodyclass" t.datetime "created_at", null: false t.boolean "is_taxonomy", default: false @@ -2491,7 +2514,7 @@ t.index ["university_id"], name: "idx_on_university_id_1be9c668d5" end - create_table "university_person_experiences", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "university_person_experiences", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.datetime "created_at", null: false t.datetime "deleted_at" t.integer "from_year" @@ -2519,7 +2542,7 @@ t.index ["university_id"], name: "idx_on_university_id_0b815cf13a" end - create_table "university_person_involvements", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "university_person_involvements", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.datetime "created_at", null: false t.datetime "deleted_at" t.integer "kind" @@ -2578,7 +2601,7 @@ t.index ["university_id"], name: "index_university_role_localizations_on_university_id" end - create_table "university_roles", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "university_roles", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.datetime "created_at", null: false t.datetime "deleted_at" t.integer "position", null: false @@ -2590,7 +2613,7 @@ t.index ["university_id"], name: "index_university_roles_on_university_id" end - create_table "user_favorites", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "user_favorites", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.uuid "about_id", null: false t.string "about_type", null: false t.datetime "created_at", null: false @@ -2600,7 +2623,7 @@ t.index ["user_id"], name: "index_user_favorites_on_user_id" end - create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "users", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.integer "brevo_contact_id" t.datetime "confirmation_sent_at", precision: nil t.string "confirmation_token" @@ -2689,6 +2712,10 @@ add_foreign_key "communication_extranet_documents", "communication_extranet_document_kinds", column: "kind_id" add_foreign_key "communication_extranet_documents", "communication_extranets", column: "extranet_id" add_foreign_key "communication_extranet_documents", "universities" + add_foreign_key "communication_extranet_invitations", "communication_extranets", column: "extranet_id" + add_foreign_key "communication_extranet_invitations", "universities" + add_foreign_key "communication_extranet_invitations", "university_people", column: "person_id" + add_foreign_key "communication_extranet_invitations", "users" add_foreign_key "communication_extranet_localizations", "communication_extranets", column: "about_id" add_foreign_key "communication_extranet_localizations", "languages" add_foreign_key "communication_extranet_localizations", "universities" diff --git a/test/mailers/previews/extranet_mailer_preview.rb b/test/mailers/previews/extranet_mailer_preview.rb index 9fc5b800b2..afc0294d3d 100644 --- a/test/mailers/previews/extranet_mailer_preview.rb +++ b/test/mailers/previews/extranet_mailer_preview.rb @@ -2,9 +2,34 @@ class ExtranetMailerPreview < BaseMailerPreview - # Preview this email at http://localhost:3000/rails/mailers/extranet_mailer/invitation_message - def invitation_message - ExtranetMailer.invitation_message(extranet, person) + # Preview this email at http://localhost:3000/rails/mailers/extranet_mailer/invitation_message_automatic + def invitation_message_automatic + ExtranetMailer.invitation_message_automatic(extranet, person) + end + + # Preview this email at http://localhost:3000/rails/mailers/extranet_mailer/invitation_message_manual + def invitation_message_manual + ExtranetMailer.invitation_message_manual(invitation) + end + + protected + + def invitation + l10n = extranet.best_localization_for(extranet.default_language) + extranet.invitations.build( + user: user, + person: person, + token: 'sample-invitation-token', + from_name: user.to_s, + from_email: user.email, + to_name: "Invité de test", + to_email: "guest@noesya.coop", + message: l10n.invitation_manual_text( + from_name: user.to_s, + from_years: '2020', + to_name: person.to_s + ) + ) end end