Skip to content

Commit 6d6e595

Browse files
kevinortiz43claude
andcommitted
Enforce profile completeness in CreateDiscussion backend
The frontend gate alone is bypassable via gRPC. Mirror the existing pattern (CreateEvent/SendMessage/etc.) and abort CreateDiscussion with FAILED_PRECONDITION for incomplete profiles, with a regression test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ee992ea commit 6d6e595

3 files changed

Lines changed: 26 additions & 0 deletions

File tree

app/backend/src/couchers/i18n/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
"host_request_too_short2_one": "Host request cannot be shorter than {{count}} character.",
9696
"host_request_too_short2_other": "Host request cannot be shorter than {{count}} characters.",
9797
"hosting_status_required": "Hosting status is required.",
98+
"incomplete_profile_create_discussion": "You have to complete your profile before you can start a discussion.",
9899
"incomplete_profile_create_event": "You have to complete your profile before you can create an event.",
99100
"incomplete_profile_create_public_trip": "You have to complete your profile before you can create a public trip.",
100101
"incomplete_profile_send_friend_request": "You have to complete your profile before you can send a friend request.",

app/backend/src/couchers/servicers/discussions.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from couchers.context import CouchersContext, make_notification_user_context
99
from couchers.db import can_moderate_node, session_scope
1010
from couchers.event_log import log_event
11+
from couchers.helpers.completed_profile import has_completed_profile
1112
from couchers.jobs.enqueue import queue_job
1213
from couchers.models import Cluster, ClusterSubscription, Discussion, ModerationObjectType, Thread, User
1314
from couchers.models.discussions import ContentChangeType, DiscussionVersion
@@ -119,6 +120,10 @@ def CreateDiscussion(
119120
if not cluster.small_community_features_enabled:
120121
context.abort_with_error_code(grpc.StatusCode.FAILED_PRECONDITION, "cannot_create_discussion")
121122

123+
user = session.execute(select(User).where(User.id == context.user_id)).scalar_one()
124+
if not has_completed_profile(session, user):
125+
context.abort_with_error_code(grpc.StatusCode.FAILED_PRECONDITION, "incomplete_profile_create_discussion")
126+
122127
thread = Thread()
123128
session.add(thread)
124129
session.flush()

app/backend/src/tests/test_discussions.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -861,3 +861,23 @@ def test_admin_delete_discussion_creates_version_record(db):
861861
assert v.old_title == "Admin will delete this"
862862
assert v.new_title is None
863863
assert v.editor_user_id == admin.id
864+
865+
866+
def test_create_discussion_incomplete_profile(db):
867+
user, token = generate_user(complete_profile=False)
868+
user2, token2 = generate_user()
869+
870+
with session_scope() as session:
871+
community_id = create_community(session, 0, 1, "Testing Community", [user2], [], None).id
872+
873+
with discussions_session(token) as api:
874+
with pytest.raises(grpc.RpcError) as e:
875+
api.CreateDiscussion(
876+
discussions_pb2.CreateDiscussionReq(
877+
title="dummy title",
878+
content="dummy content",
879+
owner_community_id=community_id,
880+
)
881+
)
882+
assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
883+
assert e.value.details() == "You have to complete your profile before you can start a discussion."

0 commit comments

Comments
 (0)