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,72 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import core_framework.domains.moderation.dependencies as moderation_deps
|
|
4
|
+
import core_framework.domains.post.dependencies as post_deps
|
|
5
|
+
from core_framework.domains.moderation import ReportCategory
|
|
6
|
+
from core_framework.domains.post import PostStatus, PostVisibility
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def create_post(
|
|
10
|
+
*,
|
|
11
|
+
author_id: str,
|
|
12
|
+
content: str,
|
|
13
|
+
visibility: PostVisibility,
|
|
14
|
+
hashtags: list[str],
|
|
15
|
+
) -> None:
|
|
16
|
+
await post_deps.post_service.add_post(
|
|
17
|
+
author_id=author_id,
|
|
18
|
+
content=content,
|
|
19
|
+
visibility=visibility,
|
|
20
|
+
hashtags=hashtags,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
async def edit_post(
|
|
25
|
+
*,
|
|
26
|
+
post_id: str,
|
|
27
|
+
author_id: str,
|
|
28
|
+
validated_update_request: dict[str, Any],
|
|
29
|
+
) -> None:
|
|
30
|
+
await post_deps.post_service.edit_post(
|
|
31
|
+
post_id=post_id,
|
|
32
|
+
author_id=author_id,
|
|
33
|
+
validated_update_request=validated_update_request,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def delete_post(*, post_id: str, author_id: str) -> None:
|
|
38
|
+
await post_deps.post_service.set_post_status_for_author(
|
|
39
|
+
post_id=post_id,
|
|
40
|
+
author_id=author_id,
|
|
41
|
+
status=PostStatus.DELETED,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def like_post(*, post_id: str, user_id: str) -> None:
|
|
46
|
+
await post_deps.post_service.add_post_like(post_id=post_id, liker_id=user_id)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
async def unlike_post(*, post_id: str, user_id: str) -> None:
|
|
50
|
+
await post_deps.post_service.remove_post_like(post_id=post_id, liker_id=user_id)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def add_post_report(
|
|
54
|
+
*,
|
|
55
|
+
reporter_id: str,
|
|
56
|
+
post_id: str,
|
|
57
|
+
category: ReportCategory,
|
|
58
|
+
reason: str,
|
|
59
|
+
) -> None:
|
|
60
|
+
await moderation_deps.moderation_service.add_post_report(
|
|
61
|
+
reporter_id=reporter_id,
|
|
62
|
+
target_id=post_id,
|
|
63
|
+
category=category,
|
|
64
|
+
reason=reason,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async def remove_post_report(*, reporter_id: str, post_id: str) -> None:
|
|
69
|
+
await moderation_deps.moderation_service.remove_post_report_by_reporter(
|
|
70
|
+
reporter_id=reporter_id,
|
|
71
|
+
target_id=post_id,
|
|
72
|
+
)
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
from asyncio import TaskGroup
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import core_framework.domains.moderation.dependencies as moderation_deps
|
|
7
|
+
import core_framework.domains.post.dependencies as post_deps
|
|
8
|
+
import core_framework.domains.user.dependencies as user_deps
|
|
9
|
+
from core_framework.domains.post import Post, PostStats
|
|
10
|
+
from core_framework.domains.user import REDACTED_AUTHOR_ID, UserIdentity
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def _retrieve_viewer_context_ids(
|
|
14
|
+
*,
|
|
15
|
+
viewer_id: str | None,
|
|
16
|
+
post_ids: set[str],
|
|
17
|
+
) -> tuple[frozenset[str] | None, frozenset[str] | None]:
|
|
18
|
+
if viewer_id is None or not post_ids:
|
|
19
|
+
return None, None
|
|
20
|
+
async with TaskGroup() as tg:
|
|
21
|
+
liked_task = tg.create_task(
|
|
22
|
+
post_deps.post_service.retrieve_post_ids_liked_by_user(
|
|
23
|
+
liker_id=viewer_id,
|
|
24
|
+
post_ids=post_ids,
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
reported_task = tg.create_task(
|
|
28
|
+
moderation_deps.moderation_service.retrieve_post_ids_reported_by_user(
|
|
29
|
+
reporter_id=viewer_id,
|
|
30
|
+
post_ids=post_ids,
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
return liked_task.result(), reported_task.result()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _serialize_post(
|
|
37
|
+
*,
|
|
38
|
+
post: Post,
|
|
39
|
+
post_stats_mapping: defaultdict[str, PostStats],
|
|
40
|
+
hashtags_mapping: defaultdict[str, list[str]],
|
|
41
|
+
user_identity_mapping: defaultdict[str, UserIdentity],
|
|
42
|
+
viewer_id: str | None = None,
|
|
43
|
+
liked_ids: frozenset[str] | None = None,
|
|
44
|
+
reported_ids: frozenset[str] | None = None,
|
|
45
|
+
) -> dict[str, Any]:
|
|
46
|
+
stats = post_stats_mapping[post.id]
|
|
47
|
+
hashtags = hashtags_mapping[post.id]
|
|
48
|
+
author_identity = user_identity_mapping[post.author_id]
|
|
49
|
+
viewer_context = None
|
|
50
|
+
if liked_ids is not None and reported_ids is not None:
|
|
51
|
+
viewer_context = {
|
|
52
|
+
"is_liked": post.id in liked_ids,
|
|
53
|
+
"is_reported": post.id in reported_ids,
|
|
54
|
+
}
|
|
55
|
+
author_context = None
|
|
56
|
+
if viewer_id is not None and viewer_id == post.author_id:
|
|
57
|
+
author_context = {
|
|
58
|
+
"can_edit": post.edited_count < post_deps.post_service.MAX_EDIT_COUNT,
|
|
59
|
+
"can_delete": True,
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
"id": post.id,
|
|
63
|
+
"author": {
|
|
64
|
+
"user_id": post.author_id,
|
|
65
|
+
"username": author_identity.username,
|
|
66
|
+
"display_name": author_identity.display_name,
|
|
67
|
+
},
|
|
68
|
+
"content": post.content,
|
|
69
|
+
"hashtags": hashtags,
|
|
70
|
+
"visibility": post.visibility,
|
|
71
|
+
"stats": {
|
|
72
|
+
"like_count": stats.like_count,
|
|
73
|
+
"comment_count": stats.comment_count,
|
|
74
|
+
"view_count": stats.view_count,
|
|
75
|
+
},
|
|
76
|
+
"engagement_allowed": post.author_id != REDACTED_AUTHOR_ID,
|
|
77
|
+
"edited_at": post.edited_at,
|
|
78
|
+
"created_at": post.created_at,
|
|
79
|
+
"viewer_context": viewer_context,
|
|
80
|
+
"author_context": author_context,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
async def retrieve_post_by_id(*, post_id: str, viewer_id: str | None) -> dict[str, Any] | None:
|
|
85
|
+
post = await post_deps.post_service.retrieve_post_by_id(post_id=post_id, viewer_id=viewer_id)
|
|
86
|
+
if post is None:
|
|
87
|
+
return None
|
|
88
|
+
post_ids = {post.id}
|
|
89
|
+
async with TaskGroup() as tg:
|
|
90
|
+
liked_reported_task = tg.create_task(_retrieve_viewer_context_ids(viewer_id=viewer_id, post_ids=post_ids))
|
|
91
|
+
stats_task = tg.create_task(post_deps.post_service.retrieve_post_stats_mapping(post_ids=post_ids))
|
|
92
|
+
hashtags_task = tg.create_task(post_deps.post_service.retrieve_hashtags_mapping(post_ids=post_ids))
|
|
93
|
+
identity_task = tg.create_task(user_deps.user_service.retrieve_user_identity_mapping(user_ids={post.author_id}))
|
|
94
|
+
liked_ids, reported_ids = liked_reported_task.result()
|
|
95
|
+
return _serialize_post(
|
|
96
|
+
post=post,
|
|
97
|
+
post_stats_mapping=stats_task.result(),
|
|
98
|
+
hashtags_mapping=hashtags_task.result(),
|
|
99
|
+
user_identity_mapping=identity_task.result(),
|
|
100
|
+
viewer_id=viewer_id,
|
|
101
|
+
liked_ids=liked_ids,
|
|
102
|
+
reported_ids=reported_ids,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
async def retrieve_posts(*, viewer_id: str | None, cursor: datetime, limit: int) -> list[dict[str, Any]]:
|
|
107
|
+
posts = await post_deps.post_service.retrieve_posts(viewer_id=viewer_id, cursor=cursor, limit=limit)
|
|
108
|
+
post_ids = {post.id for post in posts}
|
|
109
|
+
author_ids = {post.author_id for post in posts}
|
|
110
|
+
async with TaskGroup() as tg:
|
|
111
|
+
liked_reported_task = tg.create_task(_retrieve_viewer_context_ids(viewer_id=viewer_id, post_ids=post_ids))
|
|
112
|
+
stats_task = tg.create_task(post_deps.post_service.retrieve_post_stats_mapping(post_ids=post_ids))
|
|
113
|
+
hashtags_task = tg.create_task(post_deps.post_service.retrieve_hashtags_mapping(post_ids=post_ids))
|
|
114
|
+
identity_task = tg.create_task(user_deps.user_service.retrieve_user_identity_mapping(user_ids=author_ids))
|
|
115
|
+
liked_ids, reported_ids = liked_reported_task.result()
|
|
116
|
+
return [
|
|
117
|
+
_serialize_post(
|
|
118
|
+
post=post,
|
|
119
|
+
post_stats_mapping=stats_task.result(),
|
|
120
|
+
hashtags_mapping=hashtags_task.result(),
|
|
121
|
+
user_identity_mapping=identity_task.result(),
|
|
122
|
+
viewer_id=viewer_id,
|
|
123
|
+
liked_ids=liked_ids,
|
|
124
|
+
reported_ids=reported_ids,
|
|
125
|
+
)
|
|
126
|
+
for post in posts
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
async def retrieve_posts_by_user_id(
|
|
131
|
+
*,
|
|
132
|
+
user_id: str,
|
|
133
|
+
viewer_id: str | None,
|
|
134
|
+
cursor: datetime,
|
|
135
|
+
limit: int,
|
|
136
|
+
) -> list[dict[str, Any]]:
|
|
137
|
+
posts = await post_deps.post_service.retrieve_posts_by_user_id(
|
|
138
|
+
user_id=user_id,
|
|
139
|
+
cursor=cursor,
|
|
140
|
+
limit=limit,
|
|
141
|
+
viewer_id=viewer_id,
|
|
142
|
+
)
|
|
143
|
+
post_ids = {post.id for post in posts}
|
|
144
|
+
author_ids = {post.author_id for post in posts}
|
|
145
|
+
async with TaskGroup() as tg:
|
|
146
|
+
liked_reported_task = tg.create_task(_retrieve_viewer_context_ids(viewer_id=viewer_id, post_ids=post_ids))
|
|
147
|
+
stats_task = tg.create_task(post_deps.post_service.retrieve_post_stats_mapping(post_ids=post_ids))
|
|
148
|
+
hashtags_task = tg.create_task(post_deps.post_service.retrieve_hashtags_mapping(post_ids=post_ids))
|
|
149
|
+
identity_task = tg.create_task(user_deps.user_service.retrieve_user_identity_mapping(user_ids=author_ids))
|
|
150
|
+
liked_ids, reported_ids = liked_reported_task.result()
|
|
151
|
+
return [
|
|
152
|
+
_serialize_post(
|
|
153
|
+
post=post,
|
|
154
|
+
post_stats_mapping=stats_task.result(),
|
|
155
|
+
hashtags_mapping=hashtags_task.result(),
|
|
156
|
+
user_identity_mapping=identity_task.result(),
|
|
157
|
+
viewer_id=viewer_id,
|
|
158
|
+
liked_ids=liked_ids,
|
|
159
|
+
reported_ids=reported_ids,
|
|
160
|
+
)
|
|
161
|
+
for post in posts
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
async def retrieve_posts_by_hashtag(
|
|
166
|
+
*,
|
|
167
|
+
hashtag: str,
|
|
168
|
+
viewer_id: str | None,
|
|
169
|
+
cursor: datetime,
|
|
170
|
+
limit: int,
|
|
171
|
+
) -> list[dict[str, Any]]:
|
|
172
|
+
posts = await post_deps.post_service.retrieve_posts_by_hashtag(
|
|
173
|
+
hashtag=hashtag,
|
|
174
|
+
cursor=cursor,
|
|
175
|
+
limit=limit,
|
|
176
|
+
viewer_id=viewer_id,
|
|
177
|
+
)
|
|
178
|
+
post_ids = {post.id for post in posts}
|
|
179
|
+
author_ids = {post.author_id for post in posts}
|
|
180
|
+
async with TaskGroup() as tg:
|
|
181
|
+
liked_reported_task = tg.create_task(_retrieve_viewer_context_ids(viewer_id=viewer_id, post_ids=post_ids))
|
|
182
|
+
stats_task = tg.create_task(post_deps.post_service.retrieve_post_stats_mapping(post_ids=post_ids))
|
|
183
|
+
hashtags_task = tg.create_task(post_deps.post_service.retrieve_hashtags_mapping(post_ids=post_ids))
|
|
184
|
+
identity_task = tg.create_task(user_deps.user_service.retrieve_user_identity_mapping(user_ids=author_ids))
|
|
185
|
+
liked_ids, reported_ids = liked_reported_task.result()
|
|
186
|
+
return [
|
|
187
|
+
_serialize_post(
|
|
188
|
+
post=post,
|
|
189
|
+
post_stats_mapping=stats_task.result(),
|
|
190
|
+
hashtags_mapping=hashtags_task.result(),
|
|
191
|
+
user_identity_mapping=identity_task.result(),
|
|
192
|
+
viewer_id=viewer_id,
|
|
193
|
+
liked_ids=liked_ids,
|
|
194
|
+
reported_ids=reported_ids,
|
|
195
|
+
)
|
|
196
|
+
for post in posts
|
|
197
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class EventType(StrEnum):
|
|
5
|
+
LOGIN = "login"
|
|
6
|
+
VIEW = "view"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SubjectType(StrEnum):
|
|
10
|
+
POST = "post"
|
|
11
|
+
USER = "user"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RedisKeys(StrEnum):
|
|
15
|
+
TAKEN_USERNAMES = "taken_usernames"
|
|
16
|
+
USER_DETAIL = "user_detail"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class UnauthorizedException(Exception):
|
|
2
|
+
def __init__(self, message: str = "Unauthorized"):
|
|
3
|
+
self.message = message
|
|
4
|
+
super().__init__(self.message)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ForbiddenException(Exception):
|
|
8
|
+
def __init__(self, message: str = "Forbidden"):
|
|
9
|
+
self.message = message
|
|
10
|
+
super().__init__(self.message)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class UserNotFoundException(Exception):
|
|
14
|
+
def __init__(self, message: str = "User not found"):
|
|
15
|
+
self.message = message
|
|
16
|
+
super().__init__(self.message)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from typing import Final
|
|
2
|
+
|
|
3
|
+
from device_detector import DeviceDetector
|
|
4
|
+
|
|
5
|
+
MAX_METADATA_STRING_LENGTH: Final[int] = 32
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def parse_user_agent(user_agent: str | None) -> dict[str, str]:
|
|
9
|
+
result: dict[str, str] = {}
|
|
10
|
+
if not user_agent or not user_agent.strip():
|
|
11
|
+
return result
|
|
12
|
+
dd = DeviceDetector(user_agent).parse()
|
|
13
|
+
device = dd.device_type()
|
|
14
|
+
if device is not None:
|
|
15
|
+
raw = str(device)
|
|
16
|
+
if raw:
|
|
17
|
+
result["client_type"] = raw[:MAX_METADATA_STRING_LENGTH]
|
|
18
|
+
client_name = dd.client_name()
|
|
19
|
+
if client_name:
|
|
20
|
+
result["browser_name"] = client_name[:MAX_METADATA_STRING_LENGTH]
|
|
21
|
+
os_name = dd.os_name()
|
|
22
|
+
if os_name:
|
|
23
|
+
result["os_name"] = os_name[:MAX_METADATA_STRING_LENGTH]
|
|
24
|
+
return result
|
|
File without changes
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from contextlib import suppress
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from async_lru import alru_cache
|
|
7
|
+
from asyncer import asyncify
|
|
8
|
+
from firebase_admin import auth
|
|
9
|
+
from loguru import logger
|
|
10
|
+
from tenacity import retry, retry_if_exception_type, stop_after_attempt
|
|
11
|
+
|
|
12
|
+
import core_framework.core as core_fw
|
|
13
|
+
import core_framework.domains.comment.dependencies as comment_deps
|
|
14
|
+
import core_framework.domains.moderation.dependencies as moderation_deps
|
|
15
|
+
import core_framework.domains.post.dependencies as post_deps
|
|
16
|
+
import core_framework.domains.user.dependencies as user_deps
|
|
17
|
+
from core_framework.application.shared.enums import RedisKeys
|
|
18
|
+
from core_framework.application.shared.exceptions import ForbiddenException, UserNotFoundException
|
|
19
|
+
from core_framework.core.cache import invalidate_cache
|
|
20
|
+
from core_framework.domains.moderation import InternalNote, ModerationActionType, UserModeration
|
|
21
|
+
from core_framework.domains.user import UserIdentity, UserRole, UserWithProfile
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# User Queries
|
|
25
|
+
@alru_cache(ttl=3600) # 1 hour
|
|
26
|
+
async def retrieve_admin_user_ids() -> set[str]:
|
|
27
|
+
return await user_deps.user_service.retrieve_admin_user_ids()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def retrieve_users(
|
|
31
|
+
*,
|
|
32
|
+
cursor: datetime,
|
|
33
|
+
limit: int,
|
|
34
|
+
username: str | None = None,
|
|
35
|
+
role: UserRole | None = None,
|
|
36
|
+
) -> list[UserIdentity]:
|
|
37
|
+
return await user_deps.user_service.retrieve_user_identities(
|
|
38
|
+
username=username,
|
|
39
|
+
role=role,
|
|
40
|
+
cursor=cursor,
|
|
41
|
+
limit=limit,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def retrieve_user_detail(*, user_id: str) -> dict[str, Any]:
|
|
46
|
+
user_data, moderation = await _concurrent_get_user_and_moderation(user_id=user_id)
|
|
47
|
+
|
|
48
|
+
if user_data is None:
|
|
49
|
+
raise UserNotFoundException()
|
|
50
|
+
|
|
51
|
+
email: str | None = None
|
|
52
|
+
email_verified: bool = False
|
|
53
|
+
with suppress(auth.UserNotFoundError):
|
|
54
|
+
firebase_user = await asyncify(auth.get_user)(user_id)
|
|
55
|
+
email = firebase_user.email
|
|
56
|
+
email_verified = firebase_user.email_verified
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
"account": {
|
|
60
|
+
"user_id": user_id,
|
|
61
|
+
"username": user_data.identity.username,
|
|
62
|
+
"role": user_data.identity.role,
|
|
63
|
+
"email": email,
|
|
64
|
+
"email_verified": email_verified,
|
|
65
|
+
"deletion_scheduled_for": user_data.deletion_scheduled_for,
|
|
66
|
+
},
|
|
67
|
+
"profile": user_data.profile,
|
|
68
|
+
"moderation": {
|
|
69
|
+
"restriction": moderation.restriction,
|
|
70
|
+
"notes": moderation.notes,
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def _concurrent_get_user_and_moderation(*, user_id: str) -> tuple[UserWithProfile | None, UserModeration]:
|
|
76
|
+
async with asyncio.TaskGroup() as tg:
|
|
77
|
+
user_task = tg.create_task(user_deps.user_service.retrieve_user_for_detail(user_id=user_id))
|
|
78
|
+
moderation_task = tg.create_task(
|
|
79
|
+
moderation_deps.moderation_service.retrieve_user_moderation_for_detail(user_id=user_id)
|
|
80
|
+
)
|
|
81
|
+
return user_task.result(), moderation_task.result()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
async def retrieve_user_notes(
|
|
85
|
+
*,
|
|
86
|
+
user_id: str,
|
|
87
|
+
cursor: datetime,
|
|
88
|
+
limit: int,
|
|
89
|
+
) -> list[dict[str, Any]]:
|
|
90
|
+
notes = await moderation_deps.moderation_service.retrieve_internal_notes_paginated(
|
|
91
|
+
target_user_id=user_id,
|
|
92
|
+
cursor=cursor,
|
|
93
|
+
limit=limit,
|
|
94
|
+
)
|
|
95
|
+
actor_ids = {note.actor_id for note in notes}
|
|
96
|
+
user_identity_mapping = await user_deps.user_service.retrieve_user_identity_mapping(user_ids=actor_ids)
|
|
97
|
+
return [
|
|
98
|
+
{
|
|
99
|
+
"id": note.id,
|
|
100
|
+
"content": note.content,
|
|
101
|
+
"created_at": note.created_at,
|
|
102
|
+
"actor": {
|
|
103
|
+
"user_id": note.actor_id,
|
|
104
|
+
"username": user_identity_mapping[note.actor_id].username,
|
|
105
|
+
"display_name": user_identity_mapping[note.actor_id].display_name,
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
for note in notes
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
async def retrieve_user_history(
|
|
113
|
+
*,
|
|
114
|
+
user_id: str,
|
|
115
|
+
cursor: datetime,
|
|
116
|
+
limit: int,
|
|
117
|
+
) -> list[dict[str, Any]]:
|
|
118
|
+
history = await user_deps.user_service.retrieve_user_change_history(
|
|
119
|
+
user_id=user_id,
|
|
120
|
+
cursor=cursor,
|
|
121
|
+
limit=limit,
|
|
122
|
+
)
|
|
123
|
+
actor_ids = {entry.actor_id for entry in history if entry.actor_id is not None}
|
|
124
|
+
user_identity_mapping = await user_deps.user_service.retrieve_user_identity_mapping(user_ids=actor_ids)
|
|
125
|
+
return [
|
|
126
|
+
{
|
|
127
|
+
"user_id": entry.user_id,
|
|
128
|
+
"actor": {
|
|
129
|
+
"user_id": entry.actor_id,
|
|
130
|
+
"username": user_identity_mapping[entry.actor_id].username,
|
|
131
|
+
"display_name": user_identity_mapping[entry.actor_id].display_name,
|
|
132
|
+
}
|
|
133
|
+
if entry.actor_id is not None
|
|
134
|
+
else None,
|
|
135
|
+
"entity_type": entry.entity_type,
|
|
136
|
+
"field": entry.field,
|
|
137
|
+
"old_value": entry.old_value,
|
|
138
|
+
"new_value": entry.new_value,
|
|
139
|
+
"created_at": entry.created_at,
|
|
140
|
+
}
|
|
141
|
+
for entry in history
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# User Management
|
|
146
|
+
async def change_user_username(
|
|
147
|
+
*,
|
|
148
|
+
actor_id: str,
|
|
149
|
+
user_id: str,
|
|
150
|
+
new_username: str,
|
|
151
|
+
) -> None:
|
|
152
|
+
identity = await user_deps.user_service.retrieve_user_identity_strong(user_id=user_id)
|
|
153
|
+
if identity == UserIdentity.DEFAULT:
|
|
154
|
+
raise UserNotFoundException()
|
|
155
|
+
if identity.username == new_username:
|
|
156
|
+
return
|
|
157
|
+
await user_deps.user_service.change_account(
|
|
158
|
+
actor_id=actor_id,
|
|
159
|
+
user_id=user_id,
|
|
160
|
+
validated_update_request={"username": new_username},
|
|
161
|
+
)
|
|
162
|
+
await moderation_deps.moderation_service.record_moderation_action(
|
|
163
|
+
actor_id=actor_id,
|
|
164
|
+
action_type=ModerationActionType.CHANGE_USERNAME,
|
|
165
|
+
target_user_id=user_id,
|
|
166
|
+
action_metadata={"old_username": identity.username, "new_username": new_username},
|
|
167
|
+
)
|
|
168
|
+
await core_fw.redis_cache.sadd(RedisKeys.TAKEN_USERNAMES, new_username)
|
|
169
|
+
await invalidate_cache(RedisKeys.USER_DETAIL, user_id=user_id)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
async def change_user_role(
|
|
173
|
+
*,
|
|
174
|
+
actor_id: str,
|
|
175
|
+
user_id: str,
|
|
176
|
+
new_role: str,
|
|
177
|
+
) -> None:
|
|
178
|
+
identity = await user_deps.user_service.retrieve_user_identity_strong(user_id=user_id)
|
|
179
|
+
if identity == UserIdentity.DEFAULT:
|
|
180
|
+
raise UserNotFoundException()
|
|
181
|
+
if identity.role == UserRole.ADMIN:
|
|
182
|
+
raise ForbiddenException("Admin role cannot be changed via API")
|
|
183
|
+
if identity.role == new_role:
|
|
184
|
+
return
|
|
185
|
+
await user_deps.user_service.change_user_role(
|
|
186
|
+
actor_id=actor_id,
|
|
187
|
+
user_id=user_id,
|
|
188
|
+
role=new_role,
|
|
189
|
+
)
|
|
190
|
+
await moderation_deps.moderation_service.record_moderation_action(
|
|
191
|
+
actor_id=actor_id,
|
|
192
|
+
action_type=ModerationActionType.CHANGE_ROLE,
|
|
193
|
+
target_user_id=user_id,
|
|
194
|
+
action_metadata={"old_role": identity.role.value, "new_role": new_role},
|
|
195
|
+
)
|
|
196
|
+
await invalidate_cache(RedisKeys.USER_DETAIL, user_id=user_id)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
async def change_user_profile(
|
|
200
|
+
*,
|
|
201
|
+
actor_id: str,
|
|
202
|
+
user_id: str,
|
|
203
|
+
validated_update_request: dict[str, Any],
|
|
204
|
+
) -> None:
|
|
205
|
+
if not validated_update_request:
|
|
206
|
+
return
|
|
207
|
+
current_profile = await user_deps.user_service.retrieve_profile_optional_strong(user_id=user_id)
|
|
208
|
+
if current_profile is None:
|
|
209
|
+
raise UserNotFoundException()
|
|
210
|
+
actual_changes = {
|
|
211
|
+
field: value
|
|
212
|
+
for field, value in validated_update_request.items()
|
|
213
|
+
if getattr(current_profile, field, None) != value
|
|
214
|
+
}
|
|
215
|
+
if not actual_changes:
|
|
216
|
+
return
|
|
217
|
+
await user_deps.user_service.change_profile(
|
|
218
|
+
actor_id=actor_id,
|
|
219
|
+
user_id=user_id,
|
|
220
|
+
validated_update_request=actual_changes,
|
|
221
|
+
)
|
|
222
|
+
await moderation_deps.moderation_service.record_moderation_action(
|
|
223
|
+
actor_id=actor_id,
|
|
224
|
+
action_type=ModerationActionType.CHANGE_PROFILE,
|
|
225
|
+
target_user_id=user_id,
|
|
226
|
+
action_metadata={"fields": list(actual_changes.keys())},
|
|
227
|
+
)
|
|
228
|
+
await invalidate_cache(RedisKeys.USER_DETAIL, user_id=user_id)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
async def add_user_note(*, actor_id: str, user_id: str, content: str) -> InternalNote:
|
|
232
|
+
identity = await user_deps.user_service.retrieve_user_identity_strong(user_id=user_id)
|
|
233
|
+
if identity == UserIdentity.DEFAULT:
|
|
234
|
+
raise UserNotFoundException()
|
|
235
|
+
note = await moderation_deps.moderation_service.add_internal_note(
|
|
236
|
+
actor_id=actor_id,
|
|
237
|
+
target_user_id=user_id,
|
|
238
|
+
content=content,
|
|
239
|
+
)
|
|
240
|
+
await moderation_deps.moderation_service.record_moderation_action(
|
|
241
|
+
actor_id=actor_id,
|
|
242
|
+
action_type=ModerationActionType.ADD_NOTE,
|
|
243
|
+
target_user_id=user_id,
|
|
244
|
+
action_metadata={"note_id": note.id, "content": content},
|
|
245
|
+
)
|
|
246
|
+
return note
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
async def remove_user_note(*, actor_id: str, user_id: str, note_id: int) -> None:
|
|
250
|
+
note = await moderation_deps.moderation_service.retrieve_internal_note_strong(
|
|
251
|
+
note_id=note_id,
|
|
252
|
+
target_user_id=user_id,
|
|
253
|
+
)
|
|
254
|
+
if note is None:
|
|
255
|
+
return
|
|
256
|
+
await moderation_deps.moderation_service.remove_internal_note(
|
|
257
|
+
note_id=note_id,
|
|
258
|
+
target_user_id=user_id,
|
|
259
|
+
)
|
|
260
|
+
await moderation_deps.moderation_service.record_moderation_action(
|
|
261
|
+
actor_id=actor_id,
|
|
262
|
+
action_type=ModerationActionType.DELETE_NOTE,
|
|
263
|
+
target_user_id=user_id,
|
|
264
|
+
action_metadata={"note_id": note_id, "content": note.content},
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
async def remove_user(*, actor_id: str, user_id: str) -> None:
|
|
269
|
+
try:
|
|
270
|
+
identity = await user_deps.user_service.retrieve_user_identity_strong(user_id=user_id)
|
|
271
|
+
await _remove_user(user_id=user_id)
|
|
272
|
+
await invalidate_cache(RedisKeys.USER_DETAIL, user_id=user_id)
|
|
273
|
+
if identity != UserIdentity.DEFAULT:
|
|
274
|
+
await moderation_deps.moderation_service.record_moderation_action(
|
|
275
|
+
actor_id=actor_id,
|
|
276
|
+
action_type=ModerationActionType.DELETE_USER,
|
|
277
|
+
target_user_id=user_id,
|
|
278
|
+
action_metadata={"username": identity.username},
|
|
279
|
+
)
|
|
280
|
+
except Exception as e:
|
|
281
|
+
logger.error(f"Error removing user {user_id=}: {e}")
|
|
282
|
+
raise
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@retry(
|
|
286
|
+
stop=stop_after_attempt(3),
|
|
287
|
+
retry=retry_if_exception_type(Exception),
|
|
288
|
+
reraise=True,
|
|
289
|
+
)
|
|
290
|
+
async def _remove_user(*, user_id: str) -> None:
|
|
291
|
+
await moderation_deps.moderation_service.remove_user(user_id=user_id)
|
|
292
|
+
await comment_deps.comment_service.remove_user(user_id=user_id)
|
|
293
|
+
await post_deps.post_service.remove_user(user_id=user_id)
|
|
294
|
+
await user_deps.user_service.remove_user(user_id=user_id)
|
|
295
|
+
try:
|
|
296
|
+
await asyncify(auth.revoke_refresh_tokens)(user_id)
|
|
297
|
+
except Exception as e:
|
|
298
|
+
logger.error(f"Failed to revoke refresh tokens for deleted user {user_id=}: {e}")
|