Skip to content

Commit 0cebdce

Browse files
committed
Merge remote-tracking branch 'origin/develop' into backend/feature/reorder-user-communities
2 parents 9fe811f + f869324 commit 0cebdce

53 files changed

Lines changed: 1807 additions & 974 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.vscode/settings.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@
5555
"editor.tabSize": 2,
5656
"editor.insertSpaces": true,
5757
"files.trimTrailingWhitespace": true,
58-
"eslint.useFlatConfig": false,
5958
"python-env.workspaceSearchPaths": [ "app/backend/.venv" ],
6059
"python.testing.cwd": "app/backend/src/tests",
6160
"python.testing.unittestEnabled": false,

app/backend/src/couchers/email/locales/it.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
"generic": {
33
"greeting_line": "Ciao {{name}},",
44
"security_warning_contact_support": "Se non sei stato tu, contattaci a {{ email_link }} e risolveremo il prima possibile.",
5-
"do_not_reply_request": "Non rispondere a questa mail. Usa uno dei pulsanti sopra per rispondere a questa richiesta."
5+
"do_not_reply_request": "Non rispondere a questa mail. Usa uno dei pulsanti sopra per rispondere a questa richiesta.",
6+
"thanks": "Grazie!",
7+
"closing_lines": {
8+
"default": "Saluti, <br>Il Team Couchers.org",
9+
"founders": "Aapeli and Itsi,<br>Fondatori Couchers.org",
10+
"aapeli": "Aapeli<br>Co-fondatore Couchers.org<br>{{ profile_link }}"
11+
}
612
}
713
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
"incomplete_profile_create_discussion": "You have to complete your profile before you can start a discussion.",
9999
"incomplete_profile_create_event": "You have to complete your profile before you can create an event.",
100100
"incomplete_profile_create_public_trip": "You have to complete your profile before you can create a public trip.",
101+
"incomplete_profile_post_comment": "You have to complete your profile before you can post a comment.",
101102
"incomplete_profile_send_friend_request": "You have to complete your profile before you can send a friend request.",
102103
"incomplete_profile_send_message": "You have to complete your profile before you can send a message.",
103104
"incomplete_profile_send_request": "You have to complete your profile before you can send a request.",

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
"date_to_after_one_year": "Non puoi chiedere di essere ospitato per più di un anno.",
2020
"discussion_not_found": "Discussione non trovata.",
2121
"account_not_found": "Non è stato trovato alcun account con questo username o email.",
22-
"address_line_1_required": "La linea 1 dell'indirizzo è obbligatoria."
22+
"address_line_1_required": "La linea 1 dell'indirizzo è obbligatoria.",
23+
"city_required": "La città è obbligatoria.",
24+
"already_in_chat": "L'utente è già nella chat."
2325
}
2426
}

app/backend/src/couchers/i18n/locales/zh-Hans.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"errors": {
3-
"account_not_found": "未找到使用该用户名或邮箱的账户",
4-
"account_suspended": "您的账户已被停用",
3+
"account_not_found": "未找到使用该用户名或邮箱的账号",
4+
"account_suspended": "您的账号已被暂停使用",
55
"address_line_1_required": "必须填写地址第1行。",
66
"already_admin": "该用户已经是管理员。",
77
"already_have_dm": "您已经和该用户有私信聊天。",
@@ -18,7 +18,7 @@
1818
"cannot_leave_containing_community": "您的个人资料位置位于该社区内,因此您不能退出。不过,您可以在账户设置中调整通知。",
1919
"cant_add_self": "您不能把自己加入群聊。",
2020
"cant_block_self": "您不能屏蔽自己。",
21-
"cant_friend_self": "您不能和自己成为好友",
21+
"cant_friend_self": "您无法将自己添加为好友",
2222
"cant_invite_self": "您不能邀请自己进入聊天。",
2323
"cant_invite_to_dm": "您不能邀请其他用户加入私信聊天。",
2424
"can_only_refer_friends": "您只能为已确认的好友写评语。",

app/backend/src/couchers/notifications/locales/it.json

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,37 @@
22
"account_deletion": {
33
"complete": {
44
"push": {
5-
"title": "Account cancellato"
6-
}
5+
"title": "Account cancellato",
6+
"ios_title": "Account eliminato",
7+
"body_one": "Puoi recuperarlo entro {{count}} giorno usando il link che ti abbiamo inviato.",
8+
"body_many": "Puoi recuperarlo entro {{count}} di giorni usando il link che ti abbiamo inviato.",
9+
"body_other": "Puoi recuperarlo entro {{count}} giorni usando il link che ti abbiamo inviato."
10+
},
11+
"event_description": "Il tuo account è stato eliminato"
712
},
813
"start": {
914
"push": {
1015
"ios_title": "E' stata richiesta la cancellazione dell'account",
11-
"body": "Usa il link che ti abbiamo inviato via email per confermare."
16+
"body": "Usa il link che ti abbiamo inviato via email per confermare.",
17+
"title": "Eliminazione account richiesta"
18+
},
19+
"event_description": "Hai iniziato l'eliminazione dell'account"
20+
},
21+
"recovered": {
22+
"event_description": "Il tuo account è stato ripristinato (non è stato cancellato)",
23+
"push": {
24+
"title": "Account ripristinato",
25+
"ios_title": "Accout ripristinato",
26+
"body": "Bentornato!"
1227
}
1328
}
1429
},
1530
"_adhoc": {
1631
"push_enabled": {
1732
"push": {
1833
"title": "Notifiche push abilitate",
19-
"body": "Da adesso riceverai le notifiche su questo dispositivo."
34+
"body": "Da adesso riceverai le notifiche su questo dispositivo.",
35+
"ios_title": "Notifiche push abilitate"
2036
}
2137
}
2238
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from couchers.context import CouchersContext, make_background_user_context, make_notification_user_context
1111
from couchers.db import session_scope
12+
from couchers.helpers.completed_profile import has_completed_profile
1213
from couchers.jobs.enqueue import queue_job
1314
from couchers.models import (
1415
Comment,
@@ -385,6 +386,10 @@ def PostReply(
385386
if depth not in (0, 1):
386387
context.abort_with_error_code(grpc.StatusCode.NOT_FOUND, "thread_not_found")
387388

389+
user = session.execute(select(User).where(User.id == context.user_id)).scalar_one()
390+
if not has_completed_profile(session, user):
391+
context.abort_with_error_code(grpc.StatusCode.FAILED_PRECONDITION, "incomplete_profile_post_comment")
392+
388393
object_to_add: Comment | Reply | None = None
389394

390395
def create_object(moderation_state_id: int) -> int:

app/backend/src/tests/test_threads.py

Lines changed: 100 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import string
22
import textwrap
3+
from datetime import timedelta
34

45
import grpc
56
import pytest
@@ -17,11 +18,12 @@
1718
User,
1819
)
1920
from couchers.models.discussions import CommentVersion, ContentChangeType, ReplyVersion
20-
from couchers.proto import moderation_pb2, threads_pb2
21+
from couchers.proto import discussions_pb2, events_pb2, moderation_pb2, threads_pb2
2122
from couchers.servicers.threads import pack_thread_id
22-
from couchers.utils import now
23+
from couchers.utils import Timestamp_from_datetime, now
2324
from tests.fixtures.db import generate_user
24-
from tests.fixtures.sessions import real_moderation_session, threads_session
25+
from tests.fixtures.sessions import discussions_session, events_session, real_moderation_session, threads_session
26+
from tests.test_communities import create_community
2527

2628

2729
@pytest.fixture(autouse=True)
@@ -539,6 +541,101 @@ def test_delete_reply_creates_version_record(db):
539541
assert v.editor_user_id == user.id
540542

541543

544+
def _create_event_and_get_thread_id(organizer, organizer_token: str) -> int:
545+
"""Helper: create an Event via the API, return its (packed) thread_id."""
546+
with session_scope() as session:
547+
create_community(session, 0, 2, "Testing Community", [organizer], [], None)
548+
549+
start_time = now() + timedelta(hours=2)
550+
end_time = start_time + timedelta(hours=3)
551+
with events_session(organizer_token) as api:
552+
res = api.CreateEvent(
553+
events_pb2.CreateEventReq(
554+
title="Dummy event",
555+
content="Dummy content.",
556+
location=events_pb2.EventLocation(
557+
address="Near Null Island",
558+
lat=0.1,
559+
lng=0.2,
560+
),
561+
start_time=Timestamp_from_datetime(start_time),
562+
end_time=Timestamp_from_datetime(end_time),
563+
timezone="UTC",
564+
)
565+
)
566+
return int(res.thread.thread_id)
567+
568+
569+
def test_post_reply_incomplete_profile_blocked_on_event_comment(db):
570+
"""An incomplete-profile user cannot post a top-level comment on an event's thread."""
571+
organizer, organizer_token = generate_user()
572+
_commenter, commenter_token = generate_user(complete_profile=False)
573+
574+
event_thread_id = _create_event_and_get_thread_id(organizer, organizer_token)
575+
576+
with threads_session(commenter_token) as api:
577+
with pytest.raises(grpc.RpcError) as e:
578+
api.PostReply(threads_pb2.PostReplyReq(thread_id=event_thread_id, content="hello"))
579+
assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
580+
581+
582+
def test_post_reply_incomplete_profile_blocked_on_event_reply(db):
583+
"""An incomplete-profile user cannot post a nested reply within an event's thread."""
584+
organizer, organizer_token = generate_user()
585+
_commenter, commenter_token = generate_user()
586+
_replier, replier_token = generate_user(complete_profile=False)
587+
588+
event_thread_id = _create_event_and_get_thread_id(organizer, organizer_token)
589+
590+
with threads_session(commenter_token) as api:
591+
comment_thread_id = api.PostReply(
592+
threads_pb2.PostReplyReq(thread_id=event_thread_id, content="top-level comment")
593+
).thread_id
594+
595+
with threads_session(replier_token) as api:
596+
with pytest.raises(grpc.RpcError) as e:
597+
api.PostReply(threads_pb2.PostReplyReq(thread_id=comment_thread_id, content="a reply"))
598+
assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
599+
600+
601+
def test_post_reply_complete_profile_allowed_on_event_comment(db):
602+
"""A complete-profile user can post a comment on an event's thread."""
603+
organizer, organizer_token = generate_user()
604+
_commenter, commenter_token = generate_user()
605+
606+
event_thread_id = _create_event_and_get_thread_id(organizer, organizer_token)
607+
608+
with threads_session(commenter_token) as api:
609+
comment_thread_id = api.PostReply(
610+
threads_pb2.PostReplyReq(thread_id=event_thread_id, content="hello")
611+
).thread_id
612+
assert comment_thread_id
613+
614+
615+
def test_post_reply_incomplete_profile_blocked_on_discussion_thread(db):
616+
"""An incomplete-profile user cannot post a comment on a discussion thread."""
617+
admin, admin_token = generate_user()
618+
_commenter, commenter_token = generate_user(complete_profile=False)
619+
620+
with session_scope() as session:
621+
community_id = create_community(session, 0, 1, "Testing Community", [admin], [], None).id
622+
623+
with discussions_session(admin_token) as api:
624+
discussion = api.CreateDiscussion(
625+
discussions_pb2.CreateDiscussionReq(
626+
title="A discussion",
627+
content="Content",
628+
owner_community_id=community_id,
629+
)
630+
)
631+
discussion_thread_id = discussion.thread.thread_id
632+
633+
with threads_session(commenter_token) as api:
634+
with pytest.raises(grpc.RpcError) as e:
635+
api.PostReply(threads_pb2.PostReplyReq(thread_id=discussion_thread_id, content="hello"))
636+
assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
637+
638+
542639
def test_edit_comment_multiple_edits_creates_multiple_version_records(db):
543640
user, token = generate_user()
544641
_, comment_thread_id = _make_thread_and_comment(token, content="v1")

app/i18n/glossary/it.csv

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
"source","target","context","translator_comments","developer_comments"
22
"Event","Evento","","",""
3-
"blog","","","",""
4-
"Strong verification","","","",""
3+
"blog","blog","","",""
4+
"Strong verification","Verifica robusta","","",""

app/mobile/i18n/locales/it.json

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,43 @@
22
"errors": {
33
"check_connection": "Controlla la tua connessione Internet e riprova.",
44
"try_again": "Riprova",
5-
"failed_to_load": "Errore di caricamento"
5+
"failed_to_load": "Errore di caricamento",
6+
"camera_permission_denied": "Per fare le foto è necessario fornire l'autorizzazione della fotocamera.",
7+
"camera_permission_required": "Permesso fotocamera richiesto",
8+
"camera_permission_explanation": "Questa applicazione ha bisogno dell'accesso alla fotocamera per fare le foto. Per favore concedere il permesso quando richiesto.",
9+
"camera_permission_denied_permanently": "L'accesso alla fotocamera è stato negato. Per fare le foto dovrai abilitare il permesso della fotocamera nelle impostazioni del tuo dispositivo."
10+
},
11+
"not_found": {
12+
"title": "Oops!",
13+
"go_home": "Vai alla schermata iniziale!",
14+
"message": "Questa schermata non esiste."
15+
},
16+
"tabs": {
17+
"messages": "Messaggi",
18+
"search": "Cerca",
19+
"events": "Eventi",
20+
"home": "Home",
21+
"communities": "Community"
22+
},
23+
"common": {
24+
"cancel": "Cancella",
25+
"add_photo": "Aggiungi foto",
26+
"choose_photo_source": "Scegli da dove caricare la foto",
27+
"take_photo": "Fai una foto",
28+
"choose_from_library": "Scegli dalla galleria",
29+
"open_settings": "Apri le impostazioni"
30+
},
31+
"update": {
32+
"required_title": "Aggiornamento richiesto",
33+
"body_warn": "Sembra che tu stia utilizzando una vecchia versione dell'app, quindi dovrai aggiornarla presto.",
34+
"body_block": "Sembra che tu stia usando una vecchia versione dell'app, quindi devi aggiornarla adesso.",
35+
"body_banned": "Sembra che la versione dell'app che stai utilizzando abbia dei bug, quindi dovrai aggiornarla.",
36+
"preamble_warn": "Siamo un piccolo team di volontari e supportare le vecchie versioni è davvero difficile, ci scusiamo per il disagio.",
37+
"preamble_block": "Siamo un piccolo team di volontari e supportare le vecchie versioni è davvero difficile, ci scusiamo per il disagio.",
38+
"preamble_banned": "Siamo un piccolo team di volontari e a volte commettiamo degli errori, ci scusiamo per il disagio!",
39+
"action_update": "Aggiorna ora",
40+
"action_reinstall": "Scarica la nuova app",
41+
"later": "Più tardi",
42+
"failed": "L'aggiornamento non può essere applicato. Riprova per favore."
643
}
744
}

0 commit comments

Comments
 (0)