core-framework 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- core_framework/__init__.py +0 -0
- core_framework/alembic/comment/alembic/README +1 -0
- core_framework/alembic/comment/alembic/env.py +72 -0
- core_framework/alembic/comment/alembic/script.py.mako +28 -0
- core_framework/alembic/comment/alembic/versions/30334fd1347b_init.py +59 -0
- core_framework/alembic/comment/alembic/versions/a2b3c4d5e6f7_improve_comment_indexes.py +54 -0
- core_framework/alembic/comment/alembic/versions/bcc8e00cfc8b_add_extra_tables.py +64 -0
- core_framework/alembic/comment/alembic/versions/d1e2f3a4b5c6_add_comment_stats_dirty_table.py +29 -0
- core_framework/alembic/comment/alembic/versions/e3f4a5b6c7d8_cascade_delete_comment_descendants.py +49 -0
- core_framework/alembic/comment/alembic/versions/f7e6d5c4b3a2_comments_path_to_ltree.py +60 -0
- core_framework/alembic/comment/alembic.ini +52 -0
- core_framework/alembic/extension/alembic/README +1 -0
- core_framework/alembic/extension/alembic/env.py +98 -0
- core_framework/alembic/extension/alembic/script.py.mako +28 -0
- core_framework/alembic/extension/alembic/versions/0389226049cb_add_pg_trgm_extension.py +25 -0
- core_framework/alembic/extension/alembic/versions/5dc58b016cf5_add_citext_extension.py +25 -0
- core_framework/alembic/extension/alembic/versions/b0ba0d8a284e_add_pg_stat_statements_extension.py +25 -0
- core_framework/alembic/extension/alembic/versions/c9d0e1f2a3b4_add_ltree_extension.py +25 -0
- core_framework/alembic/extension/alembic.ini +147 -0
- core_framework/alembic/moderation/alembic/README +1 -0
- core_framework/alembic/moderation/alembic/env.py +98 -0
- core_framework/alembic/moderation/alembic/script.py.mako +28 -0
- core_framework/alembic/moderation/alembic/versions/085ba9021850_add_category_to_user_restrictions.py +93 -0
- core_framework/alembic/moderation/alembic/versions/5f9e4fc14a41_create_moderation_appeals_table.py +69 -0
- core_framework/alembic/moderation/alembic/versions/63e37381e73b_add_user_reports_table.py +33 -0
- core_framework/alembic/moderation/alembic/versions/6a2ae31b7ac6_add_moderation_actions_table.py +34 -0
- core_framework/alembic/moderation/alembic/versions/716aa1735c03_improve_indexes.py +36 -0
- core_framework/alembic/moderation/alembic/versions/7d243ddbfde1_add_post_reports_table.py +35 -0
- core_framework/alembic/moderation/alembic/versions/8fba1f72dd46_add_indexes.py +64 -0
- core_framework/alembic/moderation/alembic/versions/95cc35a51984_update_restriction_history.py +91 -0
- core_framework/alembic/moderation/alembic/versions/9ad79d0af730_add_unique_constraint_user_reports_.py +28 -0
- core_framework/alembic/moderation/alembic/versions/a5e569f5df1a_create_user_restrictions_table.py +38 -0
- core_framework/alembic/moderation/alembic/versions/b2c3d4e5f6a7_add_indexes.py +42 -0
- core_framework/alembic/moderation/alembic/versions/c3d4e5f6a7b8_improve_report_indexes.py +48 -0
- core_framework/alembic/moderation/alembic/versions/d4af74643ff5_add_internal_notes_table.py +38 -0
- core_framework/alembic/moderation/alembic/versions/db20f2fb7390_add_comment_reports_table.py +35 -0
- core_framework/alembic/moderation/alembic/versions/e66226952ea6_add_report_category_to_user_reports_.py +54 -0
- core_framework/alembic/moderation/alembic/versions/f5e8cb275c30_enforce_1_pending_appeal.py +29 -0
- core_framework/alembic/moderation/alembic/versions/fe1faad2832d_create_restriction_history_table.py +69 -0
- core_framework/alembic/moderation/alembic.ini +147 -0
- core_framework/alembic/post/alembic/README +1 -0
- core_framework/alembic/post/alembic/env.py +97 -0
- core_framework/alembic/post/alembic/script.py.mako +28 -0
- core_framework/alembic/post/alembic/versions/51542673f5c8_add_tables_for_muted_banned_users.py +41 -0
- core_framework/alembic/post/alembic/versions/5beeeae40a4a_add_post_views_table.py +45 -0
- core_framework/alembic/post/alembic/versions/620971509a8b_init.py +55 -0
- core_framework/alembic/post/alembic/versions/a1b2c3d4e5f6_add_indexes.py +44 -0
- core_framework/alembic/post/alembic/versions/c1d2e3f4a5b6_add_post_hashtags_table.py +36 -0
- core_framework/alembic/post/alembic/versions/e56723f2afff_add_post_stats_table.py +39 -0
- core_framework/alembic/post/alembic/versions/fbc723ac58cc_add_post_likes_table.py +32 -0
- core_framework/alembic/post/alembic.ini +149 -0
- core_framework/alembic/user/alembic/README +1 -0
- core_framework/alembic/user/alembic/env.py +98 -0
- core_framework/alembic/user/alembic/script.py.mako +28 -0
- core_framework/alembic/user/alembic/versions/1a8bb99726ed_remove_avatar_id_from_users.py +81 -0
- core_framework/alembic/user/alembic/versions/2ccacf455941_improve_indexes.py +34 -0
- core_framework/alembic/user/alembic/versions/47f47ce2110e_create_user_deletions_table.py +31 -0
- core_framework/alembic/user/alembic/versions/5976db3f0175_drop_user_states.py +26 -0
- core_framework/alembic/user/alembic/versions/62417002cf32_add_indexes.py +46 -0
- core_framework/alembic/user/alembic/versions/6f7ccf3c226b_refactor_user_login_events.py +66 -0
- core_framework/alembic/user/alembic/versions/73432817015b_add_user_preferences_table.py +33 -0
- core_framework/alembic/user/alembic/versions/765bc01a7a59_create_user_blocks_table.py +33 -0
- core_framework/alembic/user/alembic/versions/7a56631f9927_create_user_login_events_table.py +49 -0
- core_framework/alembic/user/alembic/versions/831611e589bc_create_user_state.py +31 -0
- core_framework/alembic/user/alembic/versions/83c98ab2a779_add_user_profiles_table.py +88 -0
- core_framework/alembic/user/alembic/versions/8a94362cad6d_create_user_role.py +31 -0
- core_framework/alembic/user/alembic/versions/94b973923895_add_user_change_history_table.py +97 -0
- core_framework/alembic/user/alembic/versions/cbc0f4efe84f_add_avatar_id_column_to_users_table.py +31 -0
- core_framework/alembic/user/alembic/versions/d8b98ac6b073_add_index_for_get_admin_user_ids_query.py +29 -0
- core_framework/alembic/user/alembic/versions/ddb70cc09d16_create_user_states_table.py +34 -0
- core_framework/alembic/user/alembic/versions/f9ba10815ecd_add_users_table.py +33 -0
- core_framework/alembic/user/alembic.ini +147 -0
- core_framework/api/__init__.py +0 -0
- core_framework/api/admin/__init__.py +0 -0
- core_framework/api/admin/comments/router.py +69 -0
- core_framework/api/admin/comments/schemas.py +53 -0
- core_framework/api/admin/moderation/__init__.py +0 -0
- core_framework/api/admin/moderation/router.py +205 -0
- core_framework/api/admin/moderation/schemas.py +110 -0
- core_framework/api/admin/posts/router.py +62 -0
- core_framework/api/admin/posts/schemas.py +29 -0
- core_framework/api/admin/router.py +17 -0
- core_framework/api/admin/users/__init__.py +0 -0
- core_framework/api/admin/users/router.py +181 -0
- core_framework/api/admin/users/schemas.py +137 -0
- core_framework/api/auth/__init__.py +0 -0
- core_framework/api/auth/router.py +21 -0
- core_framework/api/auth/schemas.py +28 -0
- core_framework/api/comments/authenticated/router.py +126 -0
- core_framework/api/comments/authenticated/schemas.py +27 -0
- core_framework/api/comments/public/router.py +103 -0
- core_framework/api/comments/public/schemas.py +36 -0
- core_framework/api/comments/router.py +9 -0
- core_framework/api/comments/schemas.py +17 -0
- core_framework/api/dependencies.py +168 -0
- core_framework/api/events/router.py +39 -0
- core_framework/api/events/schemas.py +20 -0
- core_framework/api/posts/authenticated/router.py +83 -0
- core_framework/api/posts/authenticated/schemas.py +37 -0
- core_framework/api/posts/public/router.py +100 -0
- core_framework/api/posts/public/schemas.py +39 -0
- core_framework/api/posts/router.py +9 -0
- core_framework/api/posts/schemas.py +39 -0
- core_framework/api/router.py +19 -0
- core_framework/api/schemas.py +9 -0
- core_framework/api/system/__init__.py +0 -0
- core_framework/api/system/router.py +108 -0
- core_framework/api/users/__init__.py +0 -0
- core_framework/api/users/authenticated/__init__.py +0 -0
- core_framework/api/users/authenticated/router.py +244 -0
- core_framework/api/users/authenticated/schemas.py +81 -0
- core_framework/api/users/public/__init__.py +0 -0
- core_framework/api/users/public/router.py +25 -0
- core_framework/api/users/public/schemas.py +7 -0
- core_framework/api/users/router.py +9 -0
- core_framework/api/users/shared/schemas.py +174 -0
- core_framework/application/__init__.py +0 -0
- core_framework/application/auth/__init__.py +0 -0
- core_framework/application/auth/access_service.py +26 -0
- core_framework/application/auth/auth_service.py +10 -0
- core_framework/application/auth/models.py +10 -0
- core_framework/application/bootstrap.py +19 -0
- core_framework/application/comments/admin_service.py +236 -0
- core_framework/application/comments/aggregation_service.py +28 -0
- core_framework/application/comments/authenticated_service.py +89 -0
- core_framework/application/comments/public_service.py +218 -0
- core_framework/application/events/README.md +26 -0
- core_framework/application/events/event_service.py +51 -0
- core_framework/application/events/event_token.py +46 -0
- core_framework/application/events/models.py +9 -0
- core_framework/application/moderation/__init__.py +0 -0
- core_framework/application/moderation/appeal_service.py +98 -0
- core_framework/application/moderation/moderator_service.py +46 -0
- core_framework/application/moderation/report_service.py +180 -0
- core_framework/application/moderation/scheduled_service.py +5 -0
- core_framework/application/moderation/user_service.py +180 -0
- core_framework/application/posts/admin_service.py +104 -0
- core_framework/application/posts/aggregation_service.py +28 -0
- core_framework/application/posts/authenticated_service.py +72 -0
- core_framework/application/posts/public_service.py +197 -0
- core_framework/application/shared/__init__.py +0 -0
- core_framework/application/shared/enums.py +16 -0
- core_framework/application/shared/exceptions.py +16 -0
- core_framework/application/shared/user_agent.py +24 -0
- core_framework/application/users/__init__.py +0 -0
- core_framework/application/users/admin_service.py +298 -0
- core_framework/application/users/authenticated_service.py +179 -0
- core_framework/application/users/public_service.py +7 -0
- core_framework/application/users/scheduled_service.py +5 -0
- core_framework/bundled_alembic.py +57 -0
- core_framework/core/__init__.py +37 -0
- core_framework/core/cache.py +234 -0
- core_framework/core/context.py +14 -0
- core_framework/core/database.py +111 -0
- core_framework/core/exception_handlers/__init__.py +3 -0
- core_framework/core/exception_handlers/comment.py +99 -0
- core_framework/core/exception_handlers/common.py +5 -0
- core_framework/core/exception_handlers/moderation.py +104 -0
- core_framework/core/exception_handlers/post.py +54 -0
- core_framework/core/exception_handlers/setup.py +80 -0
- core_framework/core/exception_handlers/user.py +72 -0
- core_framework/core/http_client.py +64 -0
- core_framework/core/logging.py +99 -0
- core_framework/core/middleware.py +64 -0
- core_framework/core/observability.py +36 -0
- core_framework/core/pagination.py +203 -0
- core_framework/core/redis.py +135 -0
- core_framework/core/runtime.py +66 -0
- core_framework/core/settings.py +189 -0
- core_framework/domains/__init__.py +0 -0
- core_framework/domains/comment/README.md +243 -0
- core_framework/domains/comment/__init__.py +25 -0
- core_framework/domains/comment/constants.py +3 -0
- core_framework/domains/comment/dependencies.py +29 -0
- core_framework/domains/comment/enums.py +11 -0
- core_framework/domains/comment/exceptions.py +31 -0
- core_framework/domains/comment/models.py +54 -0
- core_framework/domains/comment/repository.py +947 -0
- core_framework/domains/comment/service.py +259 -0
- core_framework/domains/moderation/README.md +138 -0
- core_framework/domains/moderation/__init__.py +47 -0
- core_framework/domains/moderation/dependencies.py +29 -0
- core_framework/domains/moderation/enums.py +62 -0
- core_framework/domains/moderation/exceptions.py +31 -0
- core_framework/domains/moderation/models.py +94 -0
- core_framework/domains/moderation/repository.py +828 -0
- core_framework/domains/moderation/service.py +334 -0
- core_framework/domains/post/README.md +182 -0
- core_framework/domains/post/__init__.py +22 -0
- core_framework/domains/post/constants.py +3 -0
- core_framework/domains/post/dependencies.py +29 -0
- core_framework/domains/post/enums.py +18 -0
- core_framework/domains/post/exceptions.py +21 -0
- core_framework/domains/post/models.py +53 -0
- core_framework/domains/post/repository.py +791 -0
- core_framework/domains/post/service.py +204 -0
- core_framework/domains/user/README.md +74 -0
- core_framework/domains/user/__init__.py +39 -0
- core_framework/domains/user/constants.py +8 -0
- core_framework/domains/user/dependencies.py +29 -0
- core_framework/domains/user/enums.py +19 -0
- core_framework/domains/user/exceptions.py +31 -0
- core_framework/domains/user/models.py +124 -0
- core_framework/domains/user/repository.py +612 -0
- core_framework/domains/user/service.py +257 -0
- core_framework/domains/user/utils.py +182 -0
- core_framework/main.py +104 -0
- core_framework/worker/__init__.py +0 -0
- core_framework/worker/main.py +56 -0
- core_framework/worker/schedules/__init__.py +35 -0
- core_framework/worker/schedules/schedule_aggregate_comment_stats.py +32 -0
- core_framework/worker/schedules/schedule_aggregate_post_view_counts.py +28 -0
- core_framework/worker/schedules/schedule_expired_account_deletions.py +24 -0
- core_framework/worker/schedules/schedule_expired_mute_lifts.py +24 -0
- core_framework/worker/tasks/__init__.py +11 -0
- core_framework/worker/tasks/process_account_deletion.py +13 -0
- core_framework/worker/tasks/process_aggregate_comment_stats.py +19 -0
- core_framework/worker/tasks/process_aggregate_post_stats.py +12 -0
- core_framework/worker/tasks/process_mute_lift.py +13 -0
- core_framework-0.3.0.dist-info/METADATA +22 -0
- core_framework-0.3.0.dist-info/RECORD +222 -0
- core_framework-0.3.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,828 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from core_framework.core.database import Postgres
|
|
5
|
+
from core_framework.domains.moderation.enums import (
|
|
6
|
+
AppealDecision,
|
|
7
|
+
HistoryAction,
|
|
8
|
+
ModerationActionType,
|
|
9
|
+
ReportCategory,
|
|
10
|
+
RestrictionCategory,
|
|
11
|
+
RestrictionType,
|
|
12
|
+
)
|
|
13
|
+
from core_framework.domains.moderation.exceptions import (
|
|
14
|
+
AppealAlreadyDecidedException,
|
|
15
|
+
AppealNotFoundException,
|
|
16
|
+
AppealRequirementException,
|
|
17
|
+
BaseModerationException,
|
|
18
|
+
ExistingPendingAppealException,
|
|
19
|
+
)
|
|
20
|
+
from core_framework.domains.moderation.models import (
|
|
21
|
+
Appeal,
|
|
22
|
+
InternalNote,
|
|
23
|
+
ModerationAction,
|
|
24
|
+
Report,
|
|
25
|
+
RestrictionHistory,
|
|
26
|
+
UserRestriction,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ModerationRepository:
|
|
31
|
+
def __init__(self, write_database: Postgres, read_database: Postgres, strong_read_database: Postgres):
|
|
32
|
+
self.write_database = write_database
|
|
33
|
+
self.read_database = read_database
|
|
34
|
+
self.strong_read_database = strong_read_database
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def _record_to_report(record: Any) -> Report:
|
|
38
|
+
return Report(
|
|
39
|
+
id=record["id"],
|
|
40
|
+
reporter_id=record["reporter_id"],
|
|
41
|
+
target_id=record["target_id"],
|
|
42
|
+
category=ReportCategory(record["category"]),
|
|
43
|
+
reason=record["reason"],
|
|
44
|
+
created_at=record["created_at"],
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Restrictions
|
|
48
|
+
async def upsert_ban_state(
|
|
49
|
+
self,
|
|
50
|
+
*,
|
|
51
|
+
actor_id: str,
|
|
52
|
+
user_id: str,
|
|
53
|
+
reason: str,
|
|
54
|
+
category: RestrictionCategory,
|
|
55
|
+
expires_at: datetime,
|
|
56
|
+
) -> None:
|
|
57
|
+
query = """
|
|
58
|
+
merge into moderation.user_restrictions as target
|
|
59
|
+
using (values ($1, 'banned'::moderation.restriction_type, $2, $3::moderation.restriction_category, $4::timestamptz))
|
|
60
|
+
as source(user_id, restriction_type, reason, category, expires_at)
|
|
61
|
+
on target.user_id = source.user_id
|
|
62
|
+
when matched and target.restriction_type != 'banned'::moderation.restriction_type then
|
|
63
|
+
update set
|
|
64
|
+
restriction_type = source.restriction_type,
|
|
65
|
+
reason = source.reason,
|
|
66
|
+
category = source.category,
|
|
67
|
+
expires_at = source.expires_at,
|
|
68
|
+
updated_at = now()
|
|
69
|
+
when not matched then
|
|
70
|
+
insert (user_id, restriction_type, reason, category, expires_at)
|
|
71
|
+
values (source.user_id, source.restriction_type, source.reason, source.category, source.expires_at)
|
|
72
|
+
"""
|
|
73
|
+
async with self.write_database.get_transaction_with_actor_id(actor_id) as connection:
|
|
74
|
+
await connection.execute(query, user_id, reason, category, expires_at)
|
|
75
|
+
|
|
76
|
+
async def upsert_mute_state(
|
|
77
|
+
self,
|
|
78
|
+
*,
|
|
79
|
+
actor_id: str,
|
|
80
|
+
user_id: str,
|
|
81
|
+
reason: str,
|
|
82
|
+
category: RestrictionCategory,
|
|
83
|
+
expires_at: datetime,
|
|
84
|
+
) -> None:
|
|
85
|
+
query = """
|
|
86
|
+
merge into moderation.user_restrictions as target
|
|
87
|
+
using (values ($1, 'muted'::moderation.restriction_type, $2, $3::moderation.restriction_category, $4::timestamptz))
|
|
88
|
+
as source(user_id, restriction_type, reason, category, expires_at)
|
|
89
|
+
on target.user_id = source.user_id
|
|
90
|
+
when matched and target.restriction_type = 'warned'::moderation.restriction_type then
|
|
91
|
+
update set
|
|
92
|
+
restriction_type = source.restriction_type,
|
|
93
|
+
reason = source.reason,
|
|
94
|
+
category = source.category,
|
|
95
|
+
expires_at = source.expires_at,
|
|
96
|
+
updated_at = now()
|
|
97
|
+
when not matched then
|
|
98
|
+
insert (user_id, restriction_type, reason, category, expires_at)
|
|
99
|
+
values (source.user_id, source.restriction_type, source.reason, source.category, source.expires_at)
|
|
100
|
+
"""
|
|
101
|
+
async with self.write_database.get_transaction_with_actor_id(actor_id) as connection:
|
|
102
|
+
await connection.execute(query, user_id, reason, category, expires_at)
|
|
103
|
+
|
|
104
|
+
async def upsert_warn_state(
|
|
105
|
+
self,
|
|
106
|
+
*,
|
|
107
|
+
actor_id: str,
|
|
108
|
+
user_id: str,
|
|
109
|
+
reason: str,
|
|
110
|
+
category: RestrictionCategory,
|
|
111
|
+
) -> None:
|
|
112
|
+
query = """
|
|
113
|
+
merge into moderation.user_restrictions as target
|
|
114
|
+
using (values ($1, 'warned'::moderation.restriction_type, $2, $3::moderation.restriction_category))
|
|
115
|
+
as source(user_id, restriction_type, reason, category)
|
|
116
|
+
on target.user_id = source.user_id
|
|
117
|
+
when matched then
|
|
118
|
+
do nothing
|
|
119
|
+
when not matched then
|
|
120
|
+
insert (user_id, restriction_type, reason, category)
|
|
121
|
+
values (source.user_id, source.restriction_type, source.reason, source.category)
|
|
122
|
+
"""
|
|
123
|
+
async with self.write_database.get_transaction_with_actor_id(actor_id) as connection:
|
|
124
|
+
await connection.execute(query, user_id, reason, category)
|
|
125
|
+
|
|
126
|
+
async def delete_user_restriction(self, *, actor_id: str, user_id: str) -> None:
|
|
127
|
+
query = """
|
|
128
|
+
delete from moderation.user_restrictions
|
|
129
|
+
where user_id = $1
|
|
130
|
+
"""
|
|
131
|
+
async with self.write_database.get_transaction_with_actor_id(actor_id) as connection:
|
|
132
|
+
await connection.execute(query, user_id)
|
|
133
|
+
|
|
134
|
+
async def select_expired_mute_user_ids(self) -> list[str]:
|
|
135
|
+
query = """
|
|
136
|
+
select
|
|
137
|
+
user_id
|
|
138
|
+
from moderation.user_restrictions
|
|
139
|
+
where true
|
|
140
|
+
and restriction_type = 'muted'::moderation.restriction_type
|
|
141
|
+
and expires_at is not null
|
|
142
|
+
and expires_at <= now()
|
|
143
|
+
"""
|
|
144
|
+
async with self.read_database.get_connection() as connection:
|
|
145
|
+
result = await connection.fetch(query)
|
|
146
|
+
return [record["user_id"] for record in result]
|
|
147
|
+
|
|
148
|
+
# Reports
|
|
149
|
+
async def insert_user_report(
|
|
150
|
+
self,
|
|
151
|
+
*,
|
|
152
|
+
reporter_id: str,
|
|
153
|
+
target_id: str,
|
|
154
|
+
category: ReportCategory,
|
|
155
|
+
reason: str,
|
|
156
|
+
) -> None:
|
|
157
|
+
query = """
|
|
158
|
+
insert into moderation.user_reports (reporter_id, target_id, category, reason)
|
|
159
|
+
values ($1, $2, $3, $4)
|
|
160
|
+
on conflict (reporter_id, target_id) do nothing
|
|
161
|
+
"""
|
|
162
|
+
async with self.write_database.get_connection() as connection:
|
|
163
|
+
await connection.execute(query, reporter_id, target_id, category, reason)
|
|
164
|
+
|
|
165
|
+
async def select_user_reports(
|
|
166
|
+
self,
|
|
167
|
+
*,
|
|
168
|
+
reporter_id: str | None,
|
|
169
|
+
target_id: str | None,
|
|
170
|
+
cursor: datetime,
|
|
171
|
+
limit: int,
|
|
172
|
+
) -> list[Report]:
|
|
173
|
+
query = """
|
|
174
|
+
select
|
|
175
|
+
id,
|
|
176
|
+
reporter_id,
|
|
177
|
+
target_id,
|
|
178
|
+
category,
|
|
179
|
+
reason,
|
|
180
|
+
created_at
|
|
181
|
+
from moderation.user_reports
|
|
182
|
+
where true
|
|
183
|
+
and created_at <= $1
|
|
184
|
+
and ($3::text is null or reporter_id = $3)
|
|
185
|
+
and ($4::text is null or target_id = $4)
|
|
186
|
+
order by created_at desc
|
|
187
|
+
limit $2
|
|
188
|
+
"""
|
|
189
|
+
async with self.read_database.get_connection() as connection:
|
|
190
|
+
result = await connection.fetch(query, cursor, limit, reporter_id, target_id)
|
|
191
|
+
return [self._record_to_report(record) for record in result]
|
|
192
|
+
|
|
193
|
+
async def delete_user_report_by_id(self, *, report_id: int) -> None:
|
|
194
|
+
query = """
|
|
195
|
+
delete from moderation.user_reports
|
|
196
|
+
where id = $1
|
|
197
|
+
"""
|
|
198
|
+
async with self.write_database.get_connection() as connection:
|
|
199
|
+
await connection.execute(query, report_id)
|
|
200
|
+
|
|
201
|
+
async def delete_user_report_by_reporter_and_target(self, *, reporter_id: str, target_id: str) -> None:
|
|
202
|
+
query = """
|
|
203
|
+
delete from moderation.user_reports
|
|
204
|
+
where true
|
|
205
|
+
and reporter_id = $1
|
|
206
|
+
and target_id = $2
|
|
207
|
+
"""
|
|
208
|
+
async with self.write_database.get_connection() as connection:
|
|
209
|
+
await connection.execute(query, reporter_id, target_id)
|
|
210
|
+
|
|
211
|
+
async def insert_post_report(
|
|
212
|
+
self,
|
|
213
|
+
*,
|
|
214
|
+
reporter_id: str,
|
|
215
|
+
target_id: str,
|
|
216
|
+
category: ReportCategory,
|
|
217
|
+
reason: str,
|
|
218
|
+
) -> None:
|
|
219
|
+
query = """
|
|
220
|
+
insert into moderation.post_reports (reporter_id, target_id, category, reason)
|
|
221
|
+
values ($1, $2, $3, $4)
|
|
222
|
+
on conflict (reporter_id, target_id) do nothing
|
|
223
|
+
"""
|
|
224
|
+
async with self.write_database.get_connection() as connection:
|
|
225
|
+
await connection.execute(query, reporter_id, target_id, category, reason)
|
|
226
|
+
|
|
227
|
+
async def select_post_report_count(self, *, post_id: str) -> int:
|
|
228
|
+
query = """
|
|
229
|
+
select count(*)
|
|
230
|
+
from moderation.post_reports
|
|
231
|
+
where target_id = $1
|
|
232
|
+
"""
|
|
233
|
+
async with self.read_database.get_connection() as connection:
|
|
234
|
+
return await connection.fetchval(query, post_id)
|
|
235
|
+
|
|
236
|
+
async def select_post_reports(
|
|
237
|
+
self,
|
|
238
|
+
*,
|
|
239
|
+
reporter_id: str | None,
|
|
240
|
+
target_id: str | None,
|
|
241
|
+
cursor: datetime,
|
|
242
|
+
limit: int,
|
|
243
|
+
) -> list[Report]:
|
|
244
|
+
query = """
|
|
245
|
+
select
|
|
246
|
+
id,
|
|
247
|
+
reporter_id,
|
|
248
|
+
target_id,
|
|
249
|
+
category,
|
|
250
|
+
reason,
|
|
251
|
+
created_at
|
|
252
|
+
from moderation.post_reports
|
|
253
|
+
where true
|
|
254
|
+
and created_at <= $1
|
|
255
|
+
and ($3::text is null or reporter_id = $3)
|
|
256
|
+
and ($4::text is null or target_id = $4)
|
|
257
|
+
order by created_at desc
|
|
258
|
+
limit $2
|
|
259
|
+
"""
|
|
260
|
+
async with self.read_database.get_connection() as connection:
|
|
261
|
+
result = await connection.fetch(query, cursor, limit, reporter_id, target_id)
|
|
262
|
+
return [self._record_to_report(record) for record in result]
|
|
263
|
+
|
|
264
|
+
async def delete_post_report_by_id(self, *, report_id: int) -> None:
|
|
265
|
+
query = """
|
|
266
|
+
delete from moderation.post_reports
|
|
267
|
+
where id = $1
|
|
268
|
+
"""
|
|
269
|
+
async with self.write_database.get_connection() as connection:
|
|
270
|
+
await connection.execute(query, report_id)
|
|
271
|
+
|
|
272
|
+
async def delete_post_report_by_reporter_and_target(self, *, reporter_id: str, target_id: str) -> None:
|
|
273
|
+
query = """
|
|
274
|
+
delete from moderation.post_reports
|
|
275
|
+
where true
|
|
276
|
+
and reporter_id = $1
|
|
277
|
+
and target_id = $2
|
|
278
|
+
"""
|
|
279
|
+
async with self.write_database.get_connection() as connection:
|
|
280
|
+
await connection.execute(query, reporter_id, target_id)
|
|
281
|
+
|
|
282
|
+
async def delete_post_reports_by_target_id(self, *, target_id: str) -> None:
|
|
283
|
+
query = """
|
|
284
|
+
delete from moderation.post_reports
|
|
285
|
+
where target_id = $1
|
|
286
|
+
"""
|
|
287
|
+
async with self.write_database.get_connection() as connection:
|
|
288
|
+
await connection.execute(query, target_id)
|
|
289
|
+
|
|
290
|
+
async def select_post_ids_reported_by_user(self, *, reporter_id: str, post_ids: set[str]) -> list[str]:
|
|
291
|
+
if not post_ids:
|
|
292
|
+
return []
|
|
293
|
+
query = """
|
|
294
|
+
select target_id
|
|
295
|
+
from moderation.post_reports
|
|
296
|
+
where true
|
|
297
|
+
and reporter_id = $1
|
|
298
|
+
and target_id = any($2)
|
|
299
|
+
"""
|
|
300
|
+
async with self.read_database.get_connection() as connection:
|
|
301
|
+
rows = await connection.fetch(query, reporter_id, list(post_ids))
|
|
302
|
+
return [row["target_id"] for row in rows]
|
|
303
|
+
|
|
304
|
+
async def select_comment_ids_reported_by_user(self, *, reporter_id: str, comment_ids: set[str]) -> list[str]:
|
|
305
|
+
if not comment_ids:
|
|
306
|
+
return []
|
|
307
|
+
query = """
|
|
308
|
+
select target_id
|
|
309
|
+
from moderation.comment_reports
|
|
310
|
+
where true
|
|
311
|
+
and reporter_id = $1
|
|
312
|
+
and target_id = any($2)
|
|
313
|
+
"""
|
|
314
|
+
async with self.read_database.get_connection() as connection:
|
|
315
|
+
rows = await connection.fetch(query, reporter_id, list(comment_ids))
|
|
316
|
+
return [row["target_id"] for row in rows]
|
|
317
|
+
|
|
318
|
+
async def select_comment_report_count(self, *, comment_id: str) -> int:
|
|
319
|
+
query = """
|
|
320
|
+
select count(*)::int
|
|
321
|
+
from moderation.comment_reports
|
|
322
|
+
where target_id = $1
|
|
323
|
+
"""
|
|
324
|
+
async with self.read_database.get_connection() as connection:
|
|
325
|
+
return await connection.fetchval(query, comment_id) or 0
|
|
326
|
+
|
|
327
|
+
async def select_comment_reports(
|
|
328
|
+
self,
|
|
329
|
+
*,
|
|
330
|
+
reporter_id: str | None,
|
|
331
|
+
target_id: str | None,
|
|
332
|
+
cursor: datetime,
|
|
333
|
+
limit: int,
|
|
334
|
+
) -> list[Report]:
|
|
335
|
+
query = """
|
|
336
|
+
select
|
|
337
|
+
id,
|
|
338
|
+
reporter_id,
|
|
339
|
+
target_id,
|
|
340
|
+
category,
|
|
341
|
+
reason,
|
|
342
|
+
created_at
|
|
343
|
+
from moderation.comment_reports
|
|
344
|
+
where true
|
|
345
|
+
and created_at <= $1
|
|
346
|
+
and ($3::text is null or reporter_id = $3)
|
|
347
|
+
and ($4::text is null or target_id = $4)
|
|
348
|
+
order by created_at desc
|
|
349
|
+
limit $2
|
|
350
|
+
"""
|
|
351
|
+
async with self.read_database.get_connection() as connection:
|
|
352
|
+
result = await connection.fetch(query, cursor, limit, reporter_id, target_id)
|
|
353
|
+
return [self._record_to_report(record) for record in result]
|
|
354
|
+
|
|
355
|
+
async def insert_comment_report(
|
|
356
|
+
self,
|
|
357
|
+
*,
|
|
358
|
+
reporter_id: str,
|
|
359
|
+
target_id: str,
|
|
360
|
+
category: ReportCategory,
|
|
361
|
+
reason: str,
|
|
362
|
+
) -> None:
|
|
363
|
+
query = """
|
|
364
|
+
insert into moderation.comment_reports (reporter_id, target_id, category, reason)
|
|
365
|
+
values ($1, $2, $3, $4)
|
|
366
|
+
on conflict (reporter_id, target_id) do nothing
|
|
367
|
+
"""
|
|
368
|
+
async with self.write_database.get_connection() as connection:
|
|
369
|
+
await connection.execute(query, reporter_id, target_id, category, reason)
|
|
370
|
+
|
|
371
|
+
async def delete_comment_report_by_id(self, *, report_id: int) -> None:
|
|
372
|
+
query = """
|
|
373
|
+
delete from moderation.comment_reports
|
|
374
|
+
where id = $1
|
|
375
|
+
"""
|
|
376
|
+
async with self.write_database.get_connection() as connection:
|
|
377
|
+
await connection.execute(query, report_id)
|
|
378
|
+
|
|
379
|
+
async def delete_comment_report_by_reporter_and_target(
|
|
380
|
+
self,
|
|
381
|
+
*,
|
|
382
|
+
reporter_id: str,
|
|
383
|
+
target_id: str,
|
|
384
|
+
) -> None:
|
|
385
|
+
query = """
|
|
386
|
+
delete from moderation.comment_reports
|
|
387
|
+
where true
|
|
388
|
+
and reporter_id = $1
|
|
389
|
+
and target_id = $2
|
|
390
|
+
"""
|
|
391
|
+
async with self.write_database.get_connection() as connection:
|
|
392
|
+
await connection.execute(query, reporter_id, target_id)
|
|
393
|
+
|
|
394
|
+
async def delete_comment_reports_by_target_ids(self, *, target_ids: set[str]) -> None:
|
|
395
|
+
if not target_ids:
|
|
396
|
+
return
|
|
397
|
+
query = """
|
|
398
|
+
delete from moderation.comment_reports
|
|
399
|
+
where target_id = any($1::varchar[])
|
|
400
|
+
"""
|
|
401
|
+
async with self.write_database.get_connection() as connection:
|
|
402
|
+
await connection.execute(query, list(target_ids))
|
|
403
|
+
|
|
404
|
+
# Appeals
|
|
405
|
+
async def insert_appeal(self, *, user_id: str, justification: str) -> None:
|
|
406
|
+
query = """
|
|
407
|
+
with has_restriction as (
|
|
408
|
+
select exists (
|
|
409
|
+
select 1
|
|
410
|
+
from moderation.user_restrictions
|
|
411
|
+
where true
|
|
412
|
+
and user_id = $1
|
|
413
|
+
and restriction_type in ('muted', 'banned')
|
|
414
|
+
) as yes
|
|
415
|
+
),
|
|
416
|
+
ins as (
|
|
417
|
+
insert into moderation.appeals (user_id, justification)
|
|
418
|
+
select $1, $2
|
|
419
|
+
where (select yes from has_restriction)
|
|
420
|
+
on conflict (user_id) where status = 'pending' do nothing
|
|
421
|
+
returning id
|
|
422
|
+
)
|
|
423
|
+
select
|
|
424
|
+
case when ins.id is not null then 'success'
|
|
425
|
+
when not has_restriction.yes then 'restriction_required'
|
|
426
|
+
else 'conflict'
|
|
427
|
+
end as state
|
|
428
|
+
from has_restriction left join ins on true
|
|
429
|
+
"""
|
|
430
|
+
async with self.write_database.get_connection() as connection:
|
|
431
|
+
result = await connection.fetchval(query, user_id, justification)
|
|
432
|
+
if result == "success":
|
|
433
|
+
return
|
|
434
|
+
if result == "conflict":
|
|
435
|
+
raise ExistingPendingAppealException()
|
|
436
|
+
if result == "restriction_required":
|
|
437
|
+
raise AppealRequirementException()
|
|
438
|
+
raise BaseModerationException("Failed to create appeal")
|
|
439
|
+
|
|
440
|
+
async def select_appeal_strong(self, *, appeal_id: int) -> Appeal:
|
|
441
|
+
query = """
|
|
442
|
+
select
|
|
443
|
+
id,
|
|
444
|
+
user_id,
|
|
445
|
+
justification,
|
|
446
|
+
decision_reason,
|
|
447
|
+
reviewer_id,
|
|
448
|
+
status,
|
|
449
|
+
created_at,
|
|
450
|
+
updated_at
|
|
451
|
+
from moderation.appeals
|
|
452
|
+
where id = $1
|
|
453
|
+
"""
|
|
454
|
+
async with self.strong_read_database.get_connection() as connection:
|
|
455
|
+
result = await connection.fetchrow(query, appeal_id)
|
|
456
|
+
if result is None:
|
|
457
|
+
raise AppealNotFoundException()
|
|
458
|
+
return Appeal(
|
|
459
|
+
id=result["id"],
|
|
460
|
+
user_id=result["user_id"],
|
|
461
|
+
justification=result["justification"],
|
|
462
|
+
decision_reason=result["decision_reason"],
|
|
463
|
+
reviewer_id=result["reviewer_id"],
|
|
464
|
+
status=AppealDecision(result["status"]),
|
|
465
|
+
created_at=result["created_at"],
|
|
466
|
+
updated_at=result["updated_at"],
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
async def update_appeal_decision(
|
|
470
|
+
self,
|
|
471
|
+
*,
|
|
472
|
+
actor_id: str,
|
|
473
|
+
appeal_id: int,
|
|
474
|
+
decision: AppealDecision,
|
|
475
|
+
reason: str,
|
|
476
|
+
) -> None:
|
|
477
|
+
query = """
|
|
478
|
+
update moderation.appeals
|
|
479
|
+
set
|
|
480
|
+
status = $2,
|
|
481
|
+
decision_reason = $3,
|
|
482
|
+
reviewer_id = $4
|
|
483
|
+
where true
|
|
484
|
+
and id = $1
|
|
485
|
+
and status = 'pending'
|
|
486
|
+
returning id
|
|
487
|
+
"""
|
|
488
|
+
existing_appeal_query = """
|
|
489
|
+
select 1 from moderation.appeals where id = $1
|
|
490
|
+
"""
|
|
491
|
+
async with self.write_database.get_connection() as connection:
|
|
492
|
+
row = await connection.fetchrow(query, appeal_id, decision, reason, actor_id)
|
|
493
|
+
if row is None:
|
|
494
|
+
existing = await connection.fetchrow(existing_appeal_query, appeal_id)
|
|
495
|
+
if existing is None:
|
|
496
|
+
raise AppealNotFoundException()
|
|
497
|
+
else:
|
|
498
|
+
raise AppealAlreadyDecidedException()
|
|
499
|
+
|
|
500
|
+
async def select_appeals(
|
|
501
|
+
self,
|
|
502
|
+
*,
|
|
503
|
+
cursor: datetime,
|
|
504
|
+
limit: int,
|
|
505
|
+
status: AppealDecision | None,
|
|
506
|
+
user_id: str | None = None,
|
|
507
|
+
) -> list[Appeal]:
|
|
508
|
+
query = """
|
|
509
|
+
select
|
|
510
|
+
id,
|
|
511
|
+
user_id,
|
|
512
|
+
justification,
|
|
513
|
+
decision_reason,
|
|
514
|
+
reviewer_id,
|
|
515
|
+
status,
|
|
516
|
+
created_at,
|
|
517
|
+
updated_at
|
|
518
|
+
from moderation.appeals
|
|
519
|
+
where true
|
|
520
|
+
and updated_at <= $1
|
|
521
|
+
and ($3::text is null or user_id = $3)
|
|
522
|
+
and ($4::moderation.appeal_status is null or status = $4::moderation.appeal_status)
|
|
523
|
+
order by updated_at desc
|
|
524
|
+
limit $2
|
|
525
|
+
"""
|
|
526
|
+
async with self.read_database.get_connection() as connection:
|
|
527
|
+
result = await connection.fetch(query, cursor, limit, user_id, status)
|
|
528
|
+
return [
|
|
529
|
+
Appeal(
|
|
530
|
+
id=record["id"],
|
|
531
|
+
user_id=record["user_id"],
|
|
532
|
+
justification=record["justification"],
|
|
533
|
+
decision_reason=record["decision_reason"],
|
|
534
|
+
reviewer_id=record["reviewer_id"],
|
|
535
|
+
status=AppealDecision(record["status"]),
|
|
536
|
+
created_at=record["created_at"],
|
|
537
|
+
updated_at=record["updated_at"],
|
|
538
|
+
)
|
|
539
|
+
for record in result
|
|
540
|
+
]
|
|
541
|
+
|
|
542
|
+
async def delete_current_appeal_of_user(self, *, user_id: str) -> None:
|
|
543
|
+
query = """
|
|
544
|
+
delete from moderation.appeals
|
|
545
|
+
where true
|
|
546
|
+
and user_id = $1
|
|
547
|
+
and status = 'pending'
|
|
548
|
+
"""
|
|
549
|
+
async with self.write_database.get_connection() as connection:
|
|
550
|
+
await connection.execute(query, user_id)
|
|
551
|
+
|
|
552
|
+
# Internal Notes
|
|
553
|
+
async def insert_internal_note(
|
|
554
|
+
self,
|
|
555
|
+
*,
|
|
556
|
+
actor_id: str,
|
|
557
|
+
target_user_id: str,
|
|
558
|
+
content: str,
|
|
559
|
+
) -> InternalNote:
|
|
560
|
+
query = """
|
|
561
|
+
insert into moderation.internal_notes (target_user_id, actor_id, content)
|
|
562
|
+
values ($1, $2, $3)
|
|
563
|
+
returning id, target_user_id, actor_id, content, created_at
|
|
564
|
+
"""
|
|
565
|
+
async with self.write_database.get_connection() as connection:
|
|
566
|
+
result = await connection.fetchrow(query, target_user_id, actor_id, content)
|
|
567
|
+
assert result is not None
|
|
568
|
+
return InternalNote(
|
|
569
|
+
id=result["id"],
|
|
570
|
+
target_user_id=result["target_user_id"],
|
|
571
|
+
actor_id=result["actor_id"],
|
|
572
|
+
content=result["content"],
|
|
573
|
+
created_at=result["created_at"],
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
async def select_internal_note_by_id_strong(self, *, note_id: int, target_user_id: str) -> InternalNote | None:
|
|
577
|
+
query = """
|
|
578
|
+
select
|
|
579
|
+
id,
|
|
580
|
+
target_user_id,
|
|
581
|
+
actor_id,
|
|
582
|
+
content,
|
|
583
|
+
created_at
|
|
584
|
+
from moderation.internal_notes
|
|
585
|
+
where true
|
|
586
|
+
and id = $1
|
|
587
|
+
and target_user_id = $2
|
|
588
|
+
"""
|
|
589
|
+
async with self.strong_read_database.get_connection() as connection:
|
|
590
|
+
result = await connection.fetchrow(query, note_id, target_user_id)
|
|
591
|
+
if result is None:
|
|
592
|
+
return None
|
|
593
|
+
return InternalNote(
|
|
594
|
+
id=result["id"],
|
|
595
|
+
target_user_id=result["target_user_id"],
|
|
596
|
+
actor_id=result["actor_id"],
|
|
597
|
+
content=result["content"],
|
|
598
|
+
created_at=result["created_at"],
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
async def select_internal_notes(self, *, target_user_id: str) -> list[InternalNote]:
|
|
602
|
+
query = """
|
|
603
|
+
select
|
|
604
|
+
id,
|
|
605
|
+
target_user_id,
|
|
606
|
+
actor_id,
|
|
607
|
+
content,
|
|
608
|
+
created_at
|
|
609
|
+
from moderation.internal_notes
|
|
610
|
+
where target_user_id = $1
|
|
611
|
+
order by created_at desc
|
|
612
|
+
"""
|
|
613
|
+
async with self.read_database.get_connection() as connection:
|
|
614
|
+
result = await connection.fetch(query, target_user_id)
|
|
615
|
+
return [
|
|
616
|
+
InternalNote(
|
|
617
|
+
id=record["id"],
|
|
618
|
+
target_user_id=record["target_user_id"],
|
|
619
|
+
actor_id=record["actor_id"],
|
|
620
|
+
content=record["content"],
|
|
621
|
+
created_at=record["created_at"],
|
|
622
|
+
)
|
|
623
|
+
for record in result
|
|
624
|
+
]
|
|
625
|
+
|
|
626
|
+
async def select_internal_notes_paginated(
|
|
627
|
+
self,
|
|
628
|
+
*,
|
|
629
|
+
target_user_id: str,
|
|
630
|
+
cursor: datetime,
|
|
631
|
+
limit: int,
|
|
632
|
+
) -> list[InternalNote]:
|
|
633
|
+
query = """
|
|
634
|
+
select
|
|
635
|
+
id,
|
|
636
|
+
target_user_id,
|
|
637
|
+
actor_id,
|
|
638
|
+
content,
|
|
639
|
+
created_at
|
|
640
|
+
from moderation.internal_notes
|
|
641
|
+
where true
|
|
642
|
+
and target_user_id = $1
|
|
643
|
+
and created_at <= $2
|
|
644
|
+
order by created_at desc
|
|
645
|
+
limit $3
|
|
646
|
+
"""
|
|
647
|
+
async with self.read_database.get_connection() as connection:
|
|
648
|
+
result = await connection.fetch(query, target_user_id, cursor, limit)
|
|
649
|
+
return [
|
|
650
|
+
InternalNote(
|
|
651
|
+
id=record["id"],
|
|
652
|
+
target_user_id=record["target_user_id"],
|
|
653
|
+
actor_id=record["actor_id"],
|
|
654
|
+
content=record["content"],
|
|
655
|
+
created_at=record["created_at"],
|
|
656
|
+
)
|
|
657
|
+
for record in result
|
|
658
|
+
]
|
|
659
|
+
|
|
660
|
+
async def delete_internal_note(self, *, note_id: int, target_user_id: str) -> None:
|
|
661
|
+
query = """
|
|
662
|
+
delete from moderation.internal_notes
|
|
663
|
+
where true
|
|
664
|
+
and id = $1
|
|
665
|
+
and target_user_id = $2
|
|
666
|
+
"""
|
|
667
|
+
async with self.write_database.get_connection() as connection:
|
|
668
|
+
await connection.execute(query, note_id, target_user_id)
|
|
669
|
+
|
|
670
|
+
# User Moderation
|
|
671
|
+
async def _select_user_restriction_mapping_with_database(
|
|
672
|
+
self, *, user_ids: set[str], database: Postgres
|
|
673
|
+
) -> dict[str, UserRestriction]:
|
|
674
|
+
if not user_ids:
|
|
675
|
+
return {}
|
|
676
|
+
|
|
677
|
+
query = """
|
|
678
|
+
select
|
|
679
|
+
user_id,
|
|
680
|
+
restriction_type,
|
|
681
|
+
category,
|
|
682
|
+
expires_at
|
|
683
|
+
from moderation.user_restrictions
|
|
684
|
+
where user_id = any($1)
|
|
685
|
+
"""
|
|
686
|
+
async with database.get_connection() as connection:
|
|
687
|
+
result = await connection.fetch(query, user_ids)
|
|
688
|
+
return {
|
|
689
|
+
record["user_id"]: UserRestriction(
|
|
690
|
+
user_id=record["user_id"],
|
|
691
|
+
status=RestrictionType(record["restriction_type"]),
|
|
692
|
+
category=RestrictionCategory(record["category"]),
|
|
693
|
+
expires_at=record["expires_at"],
|
|
694
|
+
)
|
|
695
|
+
for record in result
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
async def select_user_restriction_mapping(self, *, user_ids: set[str]) -> dict[str, UserRestriction]:
|
|
699
|
+
return await self._select_user_restriction_mapping_with_database(
|
|
700
|
+
user_ids=user_ids,
|
|
701
|
+
database=self.read_database,
|
|
702
|
+
)
|
|
703
|
+
|
|
704
|
+
async def select_user_restriction_mapping_strong(self, *, user_ids: set[str]) -> dict[str, UserRestriction]:
|
|
705
|
+
return await self._select_user_restriction_mapping_with_database(
|
|
706
|
+
user_ids=user_ids,
|
|
707
|
+
database=self.strong_read_database,
|
|
708
|
+
)
|
|
709
|
+
|
|
710
|
+
async def delete_user(self, *, user_id: str) -> None:
|
|
711
|
+
restriction_query = """
|
|
712
|
+
delete from moderation.user_restrictions
|
|
713
|
+
where user_id = $1
|
|
714
|
+
"""
|
|
715
|
+
restriction_history_query = """
|
|
716
|
+
delete from moderation.restriction_history
|
|
717
|
+
where user_id = $1
|
|
718
|
+
"""
|
|
719
|
+
report_query = """
|
|
720
|
+
delete from moderation.user_reports
|
|
721
|
+
where target_id = $1
|
|
722
|
+
or reporter_id = $1
|
|
723
|
+
"""
|
|
724
|
+
appeal_query = """
|
|
725
|
+
delete from moderation.appeals
|
|
726
|
+
where user_id = $1
|
|
727
|
+
"""
|
|
728
|
+
internal_notes_query = """
|
|
729
|
+
delete from moderation.internal_notes
|
|
730
|
+
where target_user_id = $1
|
|
731
|
+
"""
|
|
732
|
+
async with self.write_database.get_transaction() as connection:
|
|
733
|
+
await connection.execute(restriction_query, user_id)
|
|
734
|
+
await connection.execute(restriction_history_query, user_id)
|
|
735
|
+
await connection.execute(report_query, user_id)
|
|
736
|
+
await connection.execute(appeal_query, user_id)
|
|
737
|
+
await connection.execute(internal_notes_query, user_id)
|
|
738
|
+
|
|
739
|
+
# Moderation Actions
|
|
740
|
+
async def insert_moderation_action(
|
|
741
|
+
self,
|
|
742
|
+
*,
|
|
743
|
+
actor_id: str,
|
|
744
|
+
action_type: ModerationActionType,
|
|
745
|
+
target_user_id: str | None = None,
|
|
746
|
+
action_metadata: dict[str, Any] | None = None,
|
|
747
|
+
) -> None:
|
|
748
|
+
metadata = action_metadata if action_metadata is not None else {}
|
|
749
|
+
query = """
|
|
750
|
+
insert into moderation.moderation_actions (actor_id, action_type, target_user_id, action_metadata)
|
|
751
|
+
values ($1, $2, $3, $4)
|
|
752
|
+
"""
|
|
753
|
+
async with self.write_database.get_connection() as connection:
|
|
754
|
+
await connection.execute(query, actor_id, action_type, target_user_id, metadata)
|
|
755
|
+
|
|
756
|
+
async def select_moderation_actions_strong(
|
|
757
|
+
self,
|
|
758
|
+
*,
|
|
759
|
+
actor_id: str,
|
|
760
|
+
cursor: datetime,
|
|
761
|
+
limit: int,
|
|
762
|
+
) -> list[ModerationAction]:
|
|
763
|
+
query = """
|
|
764
|
+
select
|
|
765
|
+
id,
|
|
766
|
+
actor_id,
|
|
767
|
+
action_type,
|
|
768
|
+
target_user_id,
|
|
769
|
+
action_metadata,
|
|
770
|
+
created_at
|
|
771
|
+
from moderation.moderation_actions
|
|
772
|
+
where true
|
|
773
|
+
and actor_id = $3
|
|
774
|
+
and created_at <= $1
|
|
775
|
+
order by created_at desc
|
|
776
|
+
limit $2
|
|
777
|
+
"""
|
|
778
|
+
async with self.strong_read_database.get_connection() as connection:
|
|
779
|
+
result = await connection.fetch(query, cursor, limit, actor_id)
|
|
780
|
+
return [
|
|
781
|
+
ModerationAction(
|
|
782
|
+
id=record["id"],
|
|
783
|
+
actor_id=record["actor_id"],
|
|
784
|
+
action_type=ModerationActionType(record["action_type"]),
|
|
785
|
+
target_user_id=record["target_user_id"],
|
|
786
|
+
action_metadata=record["action_metadata"],
|
|
787
|
+
created_at=record["created_at"],
|
|
788
|
+
)
|
|
789
|
+
for record in result
|
|
790
|
+
]
|
|
791
|
+
|
|
792
|
+
async def select_restriction_history(
|
|
793
|
+
self,
|
|
794
|
+
*,
|
|
795
|
+
user_id: str,
|
|
796
|
+
cursor: datetime,
|
|
797
|
+
limit: int,
|
|
798
|
+
) -> list[RestrictionHistory]:
|
|
799
|
+
query = """
|
|
800
|
+
select
|
|
801
|
+
user_id,
|
|
802
|
+
action,
|
|
803
|
+
restriction_type,
|
|
804
|
+
category,
|
|
805
|
+
reason,
|
|
806
|
+
actor_id,
|
|
807
|
+
created_at
|
|
808
|
+
from moderation.restriction_history
|
|
809
|
+
where true
|
|
810
|
+
and user_id = $1
|
|
811
|
+
and created_at <= $2
|
|
812
|
+
order by created_at desc
|
|
813
|
+
limit $3
|
|
814
|
+
"""
|
|
815
|
+
async with self.read_database.get_connection() as connection:
|
|
816
|
+
result = await connection.fetch(query, user_id, cursor, limit)
|
|
817
|
+
return [
|
|
818
|
+
RestrictionHistory(
|
|
819
|
+
user_id=record["user_id"],
|
|
820
|
+
action=HistoryAction(record["action"]),
|
|
821
|
+
restriction_type=RestrictionType(record["restriction_type"]),
|
|
822
|
+
category=RestrictionCategory(record["category"]),
|
|
823
|
+
reason=record["reason"],
|
|
824
|
+
actor_id=record["actor_id"],
|
|
825
|
+
created_at=record["created_at"],
|
|
826
|
+
)
|
|
827
|
+
for record in result
|
|
828
|
+
]
|