Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 85 additions & 23 deletions app/controllers/better_together/conversations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,33 @@ def new
end

def create # rubocop:todo Metrics/MethodLength, Metrics/AbcSize
@conversation = Conversation.new(conversation_params.merge(creator: helpers.current_person))
# Check if user supplied only disallowed participants
submitted_any = conversation_params[:participant_ids].present?
filtered_params = conversation_params_filtered
filtered_empty = Array(filtered_params[:participant_ids]).blank?

@conversation = Conversation.new(filtered_params.merge(creator: helpers.current_person))

authorize @conversation

if @conversation.save
if submitted_any && filtered_empty
@conversation.errors.add(:conversation_participants,
t('better_together.conversations.errors.no_permitted_participants'))
respond_to do |format|
format.turbo_stream do
render turbo_stream: turbo_stream.update(
'form_errors',
partial: 'layouts/better_together/errors',
locals: { object: @conversation }
), status: :unprocessable_entity
end
format.html do
# Ensure sidebar has data when rendering the new template
set_conversations
render :new, status: :unprocessable_entity
end
end
elsif @conversation.save
@conversation.participants << helpers.current_person

respond_to do |format|
Expand All @@ -45,15 +67,44 @@ def create # rubocop:todo Metrics/MethodLength, Metrics/AbcSize
locals: { object: @conversation }
)
end
format.html { render :new }
format.html do
# Ensure sidebar has data when rendering the new template
set_conversations
render :new
end
end
end
end

def update # rubocop:todo Metrics/AbcSize, Metrics/MethodLength
def update # rubocop:todo Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
authorize @conversation
ActiveRecord::Base.transaction do # rubocop:todo Metrics/BlockLength
if @conversation.update(conversation_params)
submitted_any = conversation_params[:participant_ids].present?
filtered_params = conversation_params_filtered
filtered_empty = Array(filtered_params[:participant_ids]).blank?

if submitted_any && filtered_empty
@conversation.errors.add(:conversation_participants,
t('better_together.conversations.errors.no_permitted_participants'))
respond_to do |format|
format.turbo_stream do
render turbo_stream: turbo_stream.update(
'form_errors',
partial: 'layouts/better_together/errors',
locals: { object: @conversation }
), status: :unprocessable_entity
end
format.html do
# Ensure sidebar has data when rendering the show template
set_conversations
# Ensure messages variables are set for the show template
@messages = @conversation.messages.with_all_rich_text
.includes(sender: [:string_translations]).order(:created_at)
@message = @conversation.messages.build
render :show, status: :unprocessable_entity
end
end
elsif @conversation.update(filtered_params)
@messages = @conversation.messages.with_all_rich_text.includes(sender: [:string_translations])
.order(:created_at)
@message = @conversation.messages.build
Expand Down Expand Up @@ -147,26 +198,40 @@ def leave_conversation # rubocop:todo Metrics/MethodLength, Metrics/AbcSize
private

def available_participants
participants = Person.all

unless helpers.current_person.permitted_to?('manage_platform')
# only allow messaging platform mangers unless you are a platform_manager
participants = participants.where(id: platform_manager_ids)
end

participants
# Delegate to policy to centralize participant permission logic
ConversationPolicy.new(helpers.current_user, Conversation.new).permitted_participants
end

def conversation_params
params.require(:conversation).permit(:title, participant_ids: [])
end

def set_conversation
@conversation = helpers.current_person.conversations.includes(participants: [
:string_translations,
:contact_detail,
{ profile_image_attachment: :blob }
]).find(params[:id])
# Ensure participant_ids only include people the agent is allowed to message.
# If none remain, keep it empty; creator is always added after create.
def conversation_params_filtered # rubocop:todo Metrics/AbcSize
permitted = ConversationPolicy.new(helpers.current_user, Conversation.new).permitted_participants
permitted_ids = permitted.pluck(:id)
# Always allow the current person (creator/participant) to appear in the list
permitted_ids << helpers.current_person.id if helpers.current_person
cp = conversation_params.dup
if cp[:participant_ids].present?
cp[:participant_ids] = Array(cp[:participant_ids]).map(&:presence).compact & permitted_ids
end
cp
end

def set_conversation # rubocop:todo Metrics/MethodLength
scope = helpers.current_person.conversations.includes(participants: [
:string_translations,
:contact_detail,
{ profile_image_attachment: :blob }
])
@conversation = scope.find(params[:id])
@set_conversation ||= Conversation.includes(participants: [
:string_translations,
:contact_detail,
{ profile_image_attachment: :blob }
]).find(params[:id])
end

def set_conversations
Expand All @@ -178,9 +243,6 @@ def set_conversations
]).order(updated_at: :desc).distinct(:id)
end

def platform_manager_ids
role = BetterTogether::Role.find_by(identifier: 'platform_manager')
BetterTogether::PersonPlatformMembership.where(role_id: role.id).pluck(:member_id)
end
# platform_manager_ids now inferred by policy; kept here only if needed elsewhere
end
end
1 change: 1 addition & 0 deletions app/models/better_together/person.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def self.primary_community_delegation_attrs
store_attributes :preferences do
locale String, default: I18n.default_locale.to_s
time_zone String, default: ENV.fetch('APP_TIME_ZONE', 'Newfoundland')
receive_messages_from_members Boolean, default: false
end

store_attributes :notification_preferences do
Expand Down
14 changes: 14 additions & 0 deletions app/policies/better_together/conversation_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@ def leave_conversation?
user.present? && record.participants.size > 1
end

# Returns the people that the agent is permitted to message
def permitted_participants
if permitted_to?('manage_platform')
BetterTogether::Person.all
else
role = BetterTogether::Role.find_by(identifier: 'platform_manager')
manager_ids = BetterTogether::PersonPlatformMembership.where(role_id: role.id).pluck(:member_id)
BetterTogether::Person.where(id: manager_ids)
.or(BetterTogether::Person.where('preferences @> ?',
{ receive_messages_from_members: true }.to_json))
.distinct
end
end

class Scope < ApplicationPolicy::Scope
end
end
Expand Down
6 changes: 4 additions & 2 deletions app/views/better_together/conversations/_form.html.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<%= form_with(model: conversation, data: { turbo: false, controller: 'better_together--form-validation' }) do |form| %>
<%= turbo_frame_tag 'form_errors' %>
<%= turbo_frame_tag 'form_errors' do %>
<%= render 'layouts/better_together/errors', object: conversation %>
<% end %>

<div class="mb-3">
<%= form.label :participant_ids, t('.add_participants') %>
Expand All @@ -15,4 +17,4 @@
<div>
<%= form.submit class: 'btn btn-sm btn-primary' %>
</div>
<% end %>
<% end %>
23 changes: 14 additions & 9 deletions app/views/better_together/people/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -117,15 +117,20 @@
<%= language_select_field(form:, selected_locale: person.locale) %>
<small class="form-text text-muted"><%= t('helpers.hint.person.locale') %></small>
</div>
<div class="mb-3">
<p><%= t('better_together.people.notification_preferences') %></p>
<%= render partial: 'better_together/shared/fields/toggle_switch', locals: {form:, attr: :notify_by_email} %>
<small class="form-text text-muted"><%= t('helpers.hint.person.notify_by_email') %></small>
</div>
<div class="mb-3">
<%= render partial: 'better_together/shared/fields/toggle_switch', locals: {form:, attr: :show_conversation_details} %>
<small class="form-text text-muted"><%= t('helpers.hint.person.show_conversation_details') %></small>
</div>
<div class="mb-3">
<p><%= t('better_together.people.notification_preferences') %></p>
<%= render partial: 'better_together/shared/fields/toggle_switch', locals: {form:, attr: :notify_by_email} %>
<small class="form-text text-muted"><%= t('helpers.hint.person.notify_by_email') %></small>
</div>
<div class="mb-3">
<%= render partial: 'better_together/shared/fields/toggle_switch', locals: {form:, attr: :show_conversation_details} %>
<small class="form-text text-muted"><%= t('helpers.hint.person.show_conversation_details') %></small>
</div>
<div class="mb-3">
<p><%= t('better_together.people.allow_messages_from_members') %></p>
<%= render partial: 'better_together/shared/fields/toggle_switch', locals: {form:, attr: :receive_messages_from_members} %>
<small class="form-text text-muted"><%= t('helpers.hint.person.allow_messages_from_members') %></small>
</div>
</div>

<!-- Device Permissions Tab -->
Expand Down
6 changes: 6 additions & 0 deletions app/views/better_together/people/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@
destroy_path: policy(@person).destroy? ? person_path(@person) : nil,
destroy_confirm: t('people.confirm_delete'),
destroy_aria_label: 'Delete Profile' %>
<% conversation = BetterTogether::Conversation.new %>
<% if policy(conversation).create? && policy(conversation).permitted_participants.include?(@person) %>
<%= link_to new_conversation_path(conversation: { participant_ids: [@person.id] }), class: 'btn btn-outline-primary btn-sm me-2', 'aria-label' => t('globals.message') do %>
<i class="fas fa-envelope"></i> <%= t('globals.message') %>
<% end %>
<% end %>
</div>
</div>
</div>
Expand Down
9 changes: 5 additions & 4 deletions bin/ci
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ export RAILS_ENV="${RAILS_ENV:-test}"
export NODE_ENV="${NODE_ENV:-test}"
export DISABLE_SPRING=1

# Ensure DB is ready (uses DATABASE_URL)
cd spec/dummy
# Prepare the dummy app database (uses DATABASE_URL), but run specs from project root
pushd spec/dummy >/dev/null
bundle exec rails db:prepare
popd >/dev/null

# Optional: assets if your specs need them
# bundle exec rails assets:precompile || true
# pushd spec/dummy >/dev/null && bundle exec rails assets:precompile || true; popd >/dev/null || true

# Run specs from dummy
# Run specs from project root
bundle exec rspec --format documentation
11 changes: 11 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,9 @@ en:
options_tooltip: Conversation Options
empty:
no_messages: No messages yet. Why not start the conversation?
errors:
no_permitted_participants: You can only add platform managers or members who
have opted in to receive messages.
form:
add_participants: Add participants
create_conversation: Create conversation
Expand Down Expand Up @@ -1187,6 +1190,7 @@ en:
index:
new_page: New page
people:
allow_messages_from_members: Allow messages from platform members
device_permissions:
camera: Camera
location: Location
Expand Down Expand Up @@ -1397,6 +1401,10 @@ en:
content: Content
conversation: :activerecord.models.conversation
conversation_participants: Conversation participants
conversations:
errors:
no_permitted_participants: You can only add platform managers or members who
have opted in to receive messages.
date:
abbr_day_names:
- Sun
Expand Down Expand Up @@ -1786,6 +1794,7 @@ en:
save: Save
hidden: Hidden
locale: Locale
message: Message
new: New
'no': 'No'
no_description: No description
Expand Down Expand Up @@ -1828,6 +1837,8 @@ en:
images:
cover_image: Cover image
person:
allow_messages_from_members: Opt-in to receive messages from people other
than platform managers.
cover_image: Upload a cover image to display at the top of the profile.
description: Provide a brief description or biography.
locale: Select the preferred language for the person.
Expand Down
11 changes: 11 additions & 0 deletions config/locales/es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,9 @@ es:
options_tooltip: Opciones de Conversación
empty:
no_messages: Aún no hay mensajes. ¿Por qué no iniciar la conversación?
errors:
no_permitted_participants: Solo puedes agregar administradores de la plataforma
o miembros que hayan optado por recibir mensajes.
form:
add_participants: Agregar participantes
create_conversation: Crear conversación
Expand Down Expand Up @@ -1191,6 +1194,7 @@ es:
index:
new_page: Nueva página
people:
allow_messages_from_members: Permitir mensajes de miembros de la plataforma
device_permissions:
camera: Cámara
location: Ubicación
Expand Down Expand Up @@ -1405,6 +1409,10 @@ es:
content: Contenido
conversation: :activerecord.models.conversation
conversation_participants: Participantes de la conversación
conversations:
errors:
no_permitted_participants: Solo puedes agregar administradores de la plataforma
o miembros que hayan optado por recibir mensajes.
date:
abbr_day_names: '["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]'
abbr_month_names: '[nil, "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
Expand Down Expand Up @@ -1781,6 +1789,7 @@ es:
save: Guardar
hidden: Oculto
locale: Idioma
message: Mensaje
new: Nuevo
'no': 'No'
no_description: Sin descripción
Expand Down Expand Up @@ -1824,6 +1833,8 @@ es:
images:
cover_image: Imagen de portada
person:
allow_messages_from_members: Optar por recibir mensajes de personas que no
sean administradores de la plataforma. than platform managers.
cover_image: Sube una imagen de portada para mostrar en la parte superior
del perfil.
description: Proporcione una breve descripción o biografía.
Expand Down
11 changes: 11 additions & 0 deletions config/locales/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,9 @@ fr:
empty:
no_messages: Aucun message pour l'instant. Pourquoi ne pas commencer la conversation
?
errors:
no_permitted_participants: Vous ne pouvez ajouter que des gestionnaires de
plateforme ou des membres ayant choisi de recevoir des messages.
form:
add_participants: Ajouter des participants
create_conversation: Créer une conversation
Expand Down Expand Up @@ -1197,6 +1200,7 @@ fr:
index:
new_page: Nouvelle page
people:
allow_messages_from_members: Autoriser les messages des membres de la plateforme
device_permissions:
camera: Caméra
location: Localisation
Expand Down Expand Up @@ -1414,6 +1418,10 @@ fr:
content: Contenu
conversation: :activerecord.models.conversation
conversation_participants: Participants à la conversation
conversations:
errors:
no_permitted_participants: Vous ne pouvez ajouter que des gestionnaires de plateforme
ou des membres ayant choisi de recevoir des messages.
date:
abbr_day_names:
- dim
Expand Down Expand Up @@ -1813,6 +1821,7 @@ fr:
save: Enregistrer
hidden: Caché
locale: Locale
message: Message
new: Nouveau
'no': Non
no_description: Pas de description
Expand Down Expand Up @@ -1856,6 +1865,8 @@ fr:
images:
cover_image: Cover image
person:
allow_messages_from_members: Choisir de recevoir des messages provenant de
personnes autres que les gestionnaires de la plateforme. than platform managers.
cover_image: Téléchargez une image de couverture à afficher en haut du profil.
description: Fournissez une brève description ou biographie.
locale: Sélectionnez la langue préférée pour la personne.
Expand Down
Loading
Loading