Skip to content

Commit 4390882

Browse files
authored
Merge pull request #8470 from Couchers-org/backend/feature/set-user-content-visibility-from-filter
Backend/moderation: add from_visibility filter to SetUserContentVisibility
2 parents e9d476a + a820b56 commit 4390882

3 files changed

Lines changed: 172 additions & 1 deletion

File tree

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -534,11 +534,18 @@ def UnflagContent(
534534
def SetUserContentVisibility(
535535
self, request: moderation_pb2.SetUserContentVisibilityReq, context: CouchersContext, session: Session
536536
) -> moderation_pb2.SetUserContentVisibilityRes:
537-
"""Bulk-set visibility on every UMS-governed object authored by the given user."""
537+
"""Bulk-set visibility on every UMS-governed object authored by the given user.
538+
539+
If from_visibility is non-empty, only states currently at one of those visibilities are swept.
540+
"""
538541
new_visibility = moderationvisibility2sql[request.visibility]
539542
if new_visibility is None:
540543
context.abort_with_error_code(grpc.StatusCode.INVALID_ARGUMENT, "visibility_must_be_specified")
541544

545+
from_visibilities = {moderationvisibility2sql.get(v) for v in request.from_visibility}
546+
if None in from_visibilities:
547+
context.abort_with_error_code(grpc.StatusCode.INVALID_ARGUMENT, "visibility_must_be_specified")
548+
542549
user = session.execute(select(User).where(User.id == request.user_id)).scalar_one_or_none()
543550
if not user:
544551
context.abort_with_error_code(grpc.StatusCode.NOT_FOUND, "user_not_found")
@@ -556,6 +563,8 @@ def SetUserContentVisibility(
556563

557564
updated_count = 0
558565
for moderation_state in states:
566+
if from_visibilities and moderation_state.visibility not in from_visibilities:
567+
continue
559568
if moderation_state.visibility == new_visibility:
560569
continue
561570

app/backend/src/tests/test_moderation.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2846,6 +2846,166 @@ def test_SetUserContentVisibility_user_not_found(db):
28462846
assert e.value.code() == grpc.StatusCode.NOT_FOUND
28472847

28482848

2849+
def test_SetUserContentVisibility_from_visibility_filter(db):
2850+
super_user, super_token = generate_user(is_superuser=True)
2851+
surfer, surfer_token = generate_user()
2852+
host, _ = generate_user()
2853+
2854+
today_plus_2 = (today() + timedelta(days=2)).isoformat()
2855+
today_plus_3 = (today() + timedelta(days=3)).isoformat()
2856+
with requests_session(surfer_token) as api:
2857+
hr_id = api.CreateHostRequest(
2858+
requests_pb2.CreateHostRequestReq(
2859+
host_user_id=host.id,
2860+
from_date=today_plus_2,
2861+
to_date=today_plus_3,
2862+
text=valid_request_text(),
2863+
)
2864+
).host_request_id
2865+
2866+
with real_moderation_session(super_token) as api:
2867+
api.SetUserContentVisibility(
2868+
moderation_pb2.SetUserContentVisibilityReq(
2869+
user_id=surfer.id,
2870+
visibility=moderation_pb2.MODERATION_VISIBILITY_VISIBLE,
2871+
)
2872+
)
2873+
2874+
with real_moderation_session(super_token) as api:
2875+
res = api.SetUserContentVisibility(
2876+
moderation_pb2.SetUserContentVisibilityReq(
2877+
user_id=surfer.id,
2878+
visibility=moderation_pb2.MODERATION_VISIBILITY_HIDDEN,
2879+
from_visibility=[moderation_pb2.MODERATION_VISIBILITY_SHADOWED],
2880+
)
2881+
)
2882+
assert res.updated_count == 0
2883+
2884+
with session_scope() as session:
2885+
state = _get_moderation_state(session, ModerationObjectType.host_request, hr_id)
2886+
assert state.visibility == ModerationVisibility.visible
2887+
2888+
with real_moderation_session(super_token) as api:
2889+
res = api.SetUserContentVisibility(
2890+
moderation_pb2.SetUserContentVisibilityReq(
2891+
user_id=surfer.id,
2892+
visibility=moderation_pb2.MODERATION_VISIBILITY_SHADOWED,
2893+
from_visibility=[moderation_pb2.MODERATION_VISIBILITY_VISIBLE],
2894+
)
2895+
)
2896+
assert res.updated_count == 1
2897+
2898+
with session_scope() as session:
2899+
state = _get_moderation_state(session, ModerationObjectType.host_request, hr_id)
2900+
assert state.visibility == ModerationVisibility.shadowed
2901+
2902+
2903+
def test_SetUserContentVisibility_from_visibility_multi(db):
2904+
super_user, super_token = generate_user(is_superuser=True)
2905+
surfer, surfer_token = generate_user()
2906+
host1, _ = generate_user()
2907+
host2, _ = generate_user()
2908+
2909+
today_plus_2 = (today() + timedelta(days=2)).isoformat()
2910+
today_plus_3 = (today() + timedelta(days=3)).isoformat()
2911+
with requests_session(surfer_token) as api:
2912+
hr1_id = api.CreateHostRequest(
2913+
requests_pb2.CreateHostRequestReq(
2914+
host_user_id=host1.id,
2915+
from_date=today_plus_2,
2916+
to_date=today_plus_3,
2917+
text=valid_request_text(),
2918+
)
2919+
).host_request_id
2920+
hr2_id = api.CreateHostRequest(
2921+
requests_pb2.CreateHostRequestReq(
2922+
host_user_id=host2.id,
2923+
from_date=today_plus_2,
2924+
to_date=today_plus_3,
2925+
text=valid_request_text(),
2926+
)
2927+
).host_request_id
2928+
2929+
with session_scope() as session:
2930+
state1_id = _get_moderation_state(session, ModerationObjectType.host_request, hr1_id).id
2931+
2932+
with real_moderation_session(super_token) as api:
2933+
api.ModerateContent(
2934+
moderation_pb2.ModerateContentReq(
2935+
moderation_state_id=state1_id,
2936+
action=moderation_pb2.MODERATION_ACTION_APPROVE,
2937+
visibility=moderation_pb2.MODERATION_VISIBILITY_VISIBLE,
2938+
)
2939+
)
2940+
2941+
with real_moderation_session(super_token) as api:
2942+
res = api.SetUserContentVisibility(
2943+
moderation_pb2.SetUserContentVisibilityReq(
2944+
user_id=surfer.id,
2945+
visibility=moderation_pb2.MODERATION_VISIBILITY_HIDDEN,
2946+
from_visibility=[
2947+
moderation_pb2.MODERATION_VISIBILITY_VISIBLE,
2948+
moderation_pb2.MODERATION_VISIBILITY_SHADOWED,
2949+
],
2950+
)
2951+
)
2952+
assert res.updated_count == 2
2953+
2954+
with session_scope() as session:
2955+
state1 = _get_moderation_state(session, ModerationObjectType.host_request, hr1_id)
2956+
state2 = _get_moderation_state(session, ModerationObjectType.host_request, hr2_id)
2957+
assert state1.visibility == ModerationVisibility.hidden
2958+
assert state2.visibility == ModerationVisibility.hidden
2959+
2960+
2961+
def test_SetUserContentVisibility_from_visibility_empty_is_any(db):
2962+
super_user, super_token = generate_user(is_superuser=True)
2963+
surfer, surfer_token = generate_user()
2964+
host, _ = generate_user()
2965+
2966+
today_plus_2 = (today() + timedelta(days=2)).isoformat()
2967+
today_plus_3 = (today() + timedelta(days=3)).isoformat()
2968+
with requests_session(surfer_token) as api:
2969+
hr_id = api.CreateHostRequest(
2970+
requests_pb2.CreateHostRequestReq(
2971+
host_user_id=host.id,
2972+
from_date=today_plus_2,
2973+
to_date=today_plus_3,
2974+
text=valid_request_text(),
2975+
)
2976+
).host_request_id
2977+
2978+
with real_moderation_session(super_token) as api:
2979+
res = api.SetUserContentVisibility(
2980+
moderation_pb2.SetUserContentVisibilityReq(
2981+
user_id=surfer.id,
2982+
visibility=moderation_pb2.MODERATION_VISIBILITY_HIDDEN,
2983+
from_visibility=[],
2984+
)
2985+
)
2986+
assert res.updated_count == 1
2987+
2988+
with session_scope() as session:
2989+
state = _get_moderation_state(session, ModerationObjectType.host_request, hr_id)
2990+
assert state.visibility == ModerationVisibility.hidden
2991+
2992+
2993+
def test_SetUserContentVisibility_from_visibility_unspecified_rejected(db):
2994+
super_user, super_token = generate_user(is_superuser=True)
2995+
target, _ = generate_user()
2996+
2997+
with real_moderation_session(super_token) as api:
2998+
with pytest.raises(grpc.RpcError) as e:
2999+
api.SetUserContentVisibility(
3000+
moderation_pb2.SetUserContentVisibilityReq(
3001+
user_id=target.id,
3002+
visibility=moderation_pb2.MODERATION_VISIBILITY_HIDDEN,
3003+
from_visibility=[moderation_pb2.MODERATION_VISIBILITY_UNSPECIFIED],
3004+
)
3005+
)
3006+
assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
3007+
3008+
28493009
def test_ListModerationStates_empty(db):
28503010
super_user, super_token = generate_user(is_superuser=True)
28513011

app/proto/moderation.proto

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ message SetUserContentVisibilityReq {
177177
int64 user_id = 1;
178178
ModerationVisibility visibility = 2; // must be set
179179
string reason = 3; // optional; free-form admin note recorded in ModerationLog
180+
// If non-empty, only sweep states currently at one of these visibilities. Empty = any visibility (default).
181+
repeated ModerationVisibility from_visibility = 4;
180182
}
181183

182184
message SetUserContentVisibilityRes {

0 commit comments

Comments
 (0)