Skip to content

Commit ff82b19

Browse files
committed
Message opt-in enforcement: reject all-disallowed selections with errors; add i18n; add model + request specs
1 parent 68dfe29 commit ff82b19

File tree

6 files changed

+160
-5
lines changed

6 files changed

+160
-5
lines changed

app/controllers/better_together/conversations_controller.rb

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,28 @@ def new
2525
end
2626

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

3035
authorize @conversation
3136

32-
if @conversation.save
37+
if submitted_any && filtered_empty
38+
@conversation.errors.add(:conversation_participants, t('better_together.conversations.errors.no_permitted_participants'))
39+
respond_to do |format|
40+
format.turbo_stream do
41+
render turbo_stream: turbo_stream.update(
42+
'form_errors',
43+
partial: 'layouts/better_together/errors',
44+
locals: { object: @conversation }
45+
)
46+
end
47+
format.html { render :new }
48+
end
49+
elsif @conversation.save
3350
@conversation.participants << helpers.current_person
3451

3552
respond_to do |format|
@@ -53,7 +70,23 @@ def create # rubocop:todo Metrics/MethodLength, Metrics/AbcSize
5370
def update # rubocop:todo Metrics/AbcSize, Metrics/MethodLength
5471
authorize @conversation
5572
ActiveRecord::Base.transaction do # rubocop:todo Metrics/BlockLength
56-
if @conversation.update(conversation_params)
73+
submitted_any = conversation_params[:participant_ids].present?
74+
filtered_params = conversation_params_filtered
75+
filtered_empty = Array(filtered_params[:participant_ids]).blank?
76+
77+
if submitted_any && filtered_empty
78+
@conversation.errors.add(:conversation_participants, t('better_together.conversations.errors.no_permitted_participants'))
79+
respond_to do |format|
80+
format.turbo_stream do
81+
render turbo_stream: turbo_stream.update(
82+
'form_errors',
83+
partial: 'layouts/better_together/errors',
84+
locals: { object: @conversation }
85+
)
86+
end
87+
format.html { redirect_to conversations_path, alert: t('better_together.conversations.errors.no_permitted_participants') }
88+
end
89+
elsif @conversation.update(filtered_params)
5790
@messages = @conversation.messages.with_all_rich_text.includes(sender: [:string_translations])
5891
.order(:created_at)
5992
@message = @conversation.messages.build
@@ -155,6 +188,18 @@ def conversation_params
155188
params.require(:conversation).permit(:title, participant_ids: [])
156189
end
157190

191+
# Ensure participant_ids only include people the agent is allowed to message.
192+
# If none remain, keep it empty; creator is always added after create.
193+
def conversation_params_filtered
194+
permitted = ConversationPolicy.new(helpers.current_user, Conversation.new).permitted_participants
195+
permitted_ids = permitted.pluck(:id)
196+
cp = conversation_params.dup
197+
if cp[:participant_ids].present?
198+
cp[:participant_ids] = Array(cp[:participant_ids]).map(&:presence).compact & permitted_ids
199+
end
200+
cp
201+
end
202+
158203
def set_conversation
159204
@conversation = helpers.current_person.conversations.includes(participants: [
160205
:string_translations,

config/locales/en.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2082,3 +2082,6 @@ en:
20822082
sort_by_total_views: Sort by Total Views
20832083
to_date: To Date
20842084
'yes': 'Yes'
2085+
conversations:
2086+
errors:
2087+
no_permitted_participants: You can only add platform managers or members who have opted in to receive messages.

config/locales/es.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1191,6 +1191,7 @@ es:
11911191
index:
11921192
new_page: Nueva página
11931193
people:
1194+
allow_messages_from_members: Permitir mensajes de miembros de la plataforma
11941195
device_permissions:
11951196
camera: Cámara
11961197
location: Ubicación
@@ -1245,7 +1246,7 @@ es:
12451246
platform_manager_error: Los administradores de plataforma no pueden ser bloqueados.
12461247
self_block_error: No puedes bloquearte a ti mismo.
12471248
unblocked: Persona ha sido desbloqueado exitosamente.
1248-
phone_numbers:
1249+
phone_numbers:
12491250
add: Agregar Número de Teléfono
12501251
label: Etiqueta
12511252
labels:
@@ -1781,6 +1782,7 @@ es:
17811782
save: Guardar
17821783
hidden: Oculto
17831784
locale: Idioma
1785+
message: Mensaje
17841786
new: Nuevo
17851787
'no': 'No'
17861788
no_description: Sin descripción
@@ -1824,6 +1826,8 @@ es:
18241826
images:
18251827
cover_image: Imagen de portada
18261828
person:
1829+
allow_messages_from_members: Optar por recibir mensajes de personas que no sean administradores de la plataforma.
1830+
than platform managers.
18271831
cover_image: Sube una imagen de portada para mostrar en la parte superior
18281832
del perfil.
18291833
description: Proporcione una breve descripción o biografía.
@@ -2082,3 +2086,6 @@ es:
20822086
sort_by_total_views: Ordenar por vistas totales
20832087
to_date: Hasta fecha
20842088
'yes':
2089+
conversations:
2090+
errors:
2091+
no_permitted_participants: Solo puedes agregar administradores de la plataforma o miembros que hayan optado por recibir mensajes.

config/locales/fr.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1197,6 +1197,7 @@ fr:
11971197
index:
11981198
new_page: Nouvelle page
11991199
people:
1200+
allow_messages_from_members: Autoriser les messages des membres de la plateforme
12001201
device_permissions:
12011202
camera: Caméra
12021203
location: Localisation
@@ -1252,7 +1253,7 @@ fr:
12521253
bloqués.
12531254
self_block_error: Vous ne pouvez pas vous bloquer vous-même.
12541255
unblocked: La personne a été débloqué avec succès.
1255-
phone_numbers:
1256+
phone_numbers:
12561257
add: Ajouter un numéro de téléphone
12571258
label: Libellé
12581259
labels:
@@ -1813,6 +1814,7 @@ fr:
18131814
save: Enregistrer
18141815
hidden: Caché
18151816
locale: Locale
1817+
message: Message
18161818
new: Nouveau
18171819
'no': Non
18181820
no_description: Pas de description
@@ -1856,6 +1858,8 @@ fr:
18561858
images:
18571859
cover_image: Cover image
18581860
person:
1861+
allow_messages_from_members: Choisir de recevoir des messages provenant de personnes autres que les gestionnaires de la plateforme.
1862+
than platform managers.
18591863
cover_image: Téléchargez une image de couverture à afficher en haut du profil.
18601864
description: Fournissez une brève description ou biographie.
18611865
locale: Sélectionnez la langue préférée pour la personne.
@@ -2109,3 +2113,6 @@ fr:
21092113
sort_by_total_views: Trier par vues totales
21102114
to_date: Date de fin
21112115
'yes': Oui
2116+
conversations:
2117+
errors:
2118+
no_permitted_participants: Vous ne pouvez ajouter que des gestionnaires de plateforme ou des membres ayant choisi de recevoir des messages.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
5+
RSpec.describe BetterTogether::Person, type: :model do
6+
describe 'message opt-in preference' do
7+
it 'defaults to false and can be toggled to true' do
8+
person = create(:better_together_person)
9+
expect(person.preferences['receive_messages_from_members']).to be(false)
10+
11+
person.update!(preferences: person.preferences.merge('receive_messages_from_members' => true))
12+
person.reload
13+
expect(person.preferences['receive_messages_from_members']).to be(true)
14+
end
15+
end
16+
end
17+

spec/requests/better_together/conversations_request_spec.rb

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,80 @@
5050
end
5151
end
5252
end
53+
54+
describe 'POST /conversations' do
55+
context 'as a regular member' do
56+
let!(:regular_user) { create(:user, :confirmed, email: '[email protected]', password: 'password12345') }
57+
58+
before { login(regular_user.email, 'password12345') }
59+
60+
it 'filters out non-permitted participant_ids on create' do
61+
post better_together.conversations_path(locale: I18n.default_locale), params: {
62+
conversation: {
63+
title: 'Hello',
64+
participant_ids: [opted_in_person.id, non_opted_person.id]
65+
}
66+
}
67+
expect(response).to have_http_status(:found)
68+
convo = BetterTogether::Conversation.order(created_at: :desc).first
69+
expect(convo.creator).to eq(regular_user.person)
70+
ids = convo.participants.pluck(:id)
71+
expect(ids).to include(regular_user.person.id) # creator always added
72+
expect(ids).to include(opted_in_person.id) # allowed
73+
expect(ids).not_to include(non_opted_person.id) # filtered out
74+
end
75+
76+
it 'shows an error when only non-permitted participants are submitted' do
77+
before_count = BetterTogether::Conversation.count
78+
post better_together.conversations_path(locale: I18n.default_locale), params: {
79+
conversation: {
80+
title: 'Hello',
81+
participant_ids: [non_opted_person.id]
82+
}
83+
}
84+
expect(response).to have_http_status(:ok)
85+
expect(BetterTogether::Conversation.count).to eq(before_count)
86+
expect(response.body).to include(I18n.t('better_together.conversations.errors.no_permitted_participants'))
87+
end
88+
end
89+
end
90+
91+
describe 'PATCH /conversations/:id' do
92+
context 'as a regular member' do
93+
let!(:regular_user) { create(:user, :confirmed, email: '[email protected]', password: 'password12345') }
94+
let!(:conversation) do
95+
create('better_together/conversation', creator: regular_user.person).tap do |c|
96+
c.participants << regular_user.person unless c.participants.exists?(regular_user.person.id)
97+
end
98+
end
99+
100+
before { login(regular_user.email, 'password12345') }
101+
102+
it 'does not add non-permitted participants on update' do
103+
patch better_together.conversation_path(conversation, locale: I18n.default_locale), params: {
104+
conversation: {
105+
title: conversation.title,
106+
participant_ids: [regular_user.person.id, non_opted_person.id]
107+
}
108+
}
109+
expect(response).to have_http_status(:found)
110+
conversation.reload
111+
ids = conversation.participants.pluck(:id)
112+
expect(ids).to include(regular_user.person.id)
113+
expect(ids).not_to include(non_opted_person.id)
114+
end
115+
116+
it 'shows an error when update attempts to add only non-permitted participants' do
117+
patch better_together.conversation_path(conversation, locale: I18n.default_locale), params: {
118+
conversation: {
119+
title: conversation.title,
120+
participant_ids: [non_opted_person.id]
121+
}
122+
}
123+
expect(response).to have_http_status(:found)
124+
follow_redirect!
125+
expect(response.body).to include(I18n.t('better_together.conversations.errors.no_permitted_participants'))
126+
end
127+
end
128+
end
53129
end

0 commit comments

Comments
 (0)