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,205 @@
|
|
|
1
|
+
from typing import Annotated, Any
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, BackgroundTasks, Depends, Query, Request, status
|
|
4
|
+
from firebase_admin import auth
|
|
5
|
+
|
|
6
|
+
from core_framework.api.admin.moderation.schemas import (
|
|
7
|
+
AppealDecisionRequest,
|
|
8
|
+
AppealResponse,
|
|
9
|
+
ModerationActionResponse,
|
|
10
|
+
ModerationReason,
|
|
11
|
+
ReportItem,
|
|
12
|
+
ReportType,
|
|
13
|
+
RestrictionHistoryItem,
|
|
14
|
+
validate_appeal_id,
|
|
15
|
+
validate_report_id,
|
|
16
|
+
)
|
|
17
|
+
from core_framework.api.dependencies import RequiredUserID
|
|
18
|
+
from core_framework.api.users.shared.schemas import validate_optional_user_id, validate_user_id
|
|
19
|
+
from core_framework.application.moderation.appeal_service import decide_appeal, retrieve_appeals
|
|
20
|
+
from core_framework.application.moderation.moderator_service import retrieve_moderation_actions_strong
|
|
21
|
+
from core_framework.application.moderation.report_service import retrieve_reports
|
|
22
|
+
from core_framework.application.moderation.user_service import (
|
|
23
|
+
ban_user,
|
|
24
|
+
clear_user_restriction,
|
|
25
|
+
mute_user,
|
|
26
|
+
remove_comment_report,
|
|
27
|
+
remove_post_report,
|
|
28
|
+
remove_user_report,
|
|
29
|
+
retrieve_restriction_history,
|
|
30
|
+
warn_user,
|
|
31
|
+
)
|
|
32
|
+
from core_framework.core.pagination import (
|
|
33
|
+
PaginationCursorType,
|
|
34
|
+
PaginationResponse,
|
|
35
|
+
TokenCursorQueryParams,
|
|
36
|
+
paginate_cursor,
|
|
37
|
+
)
|
|
38
|
+
from core_framework.domains.moderation import AppealDecision
|
|
39
|
+
|
|
40
|
+
router = APIRouter(prefix="/moderation")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@router.get("/moderators/{moderator_id}/actions", response_model=PaginationResponse[ModerationActionResponse])
|
|
44
|
+
async def get_moderator_actions(
|
|
45
|
+
request: Request,
|
|
46
|
+
moderator_id: str,
|
|
47
|
+
pagination_params: Annotated[TokenCursorQueryParams, Depends()],
|
|
48
|
+
) -> Any:
|
|
49
|
+
validated_moderator_id = validate_user_id(user_id=moderator_id)
|
|
50
|
+
actions = await retrieve_moderation_actions_strong(
|
|
51
|
+
actor_id=validated_moderator_id.root,
|
|
52
|
+
cursor=pagination_params.cursor,
|
|
53
|
+
limit=pagination_params.limit,
|
|
54
|
+
)
|
|
55
|
+
return paginate_cursor(
|
|
56
|
+
request,
|
|
57
|
+
actions,
|
|
58
|
+
pagination_params.limit,
|
|
59
|
+
PaginationCursorType.CREATED_AT,
|
|
60
|
+
retrieved=pagination_params.retrieved,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@router.get("/appeals", response_model=PaginationResponse[AppealResponse])
|
|
65
|
+
async def get_appeals(
|
|
66
|
+
request: Request,
|
|
67
|
+
pagination_params: Annotated[TokenCursorQueryParams, Depends()],
|
|
68
|
+
status: Annotated[AppealDecision | None, Query()] = None,
|
|
69
|
+
) -> Any:
|
|
70
|
+
appeals = await retrieve_appeals(
|
|
71
|
+
status=status,
|
|
72
|
+
cursor=pagination_params.cursor,
|
|
73
|
+
limit=pagination_params.limit,
|
|
74
|
+
)
|
|
75
|
+
return paginate_cursor(
|
|
76
|
+
request,
|
|
77
|
+
appeals,
|
|
78
|
+
pagination_params.limit,
|
|
79
|
+
PaginationCursorType.UPDATED_AT,
|
|
80
|
+
retrieved=pagination_params.retrieved,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@router.patch("/appeals/{appeal_id}", response_model=AppealResponse)
|
|
85
|
+
async def patch_appeal(
|
|
86
|
+
actor_id: RequiredUserID,
|
|
87
|
+
appeal_id: int,
|
|
88
|
+
decision_request: AppealDecisionRequest,
|
|
89
|
+
) -> Any:
|
|
90
|
+
validated_appeal_id = validate_appeal_id(appeal_id=appeal_id)
|
|
91
|
+
return await decide_appeal(
|
|
92
|
+
actor_id=actor_id.root,
|
|
93
|
+
appeal_id=validated_appeal_id.root,
|
|
94
|
+
decision=decision_request.decision,
|
|
95
|
+
reason=decision_request.reason,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@router.put("/users/{target_user_id}/ban", status_code=status.HTTP_204_NO_CONTENT)
|
|
100
|
+
async def put_user_ban(
|
|
101
|
+
actor_id: RequiredUserID,
|
|
102
|
+
target_user_id: str,
|
|
103
|
+
moderation_reason: ModerationReason,
|
|
104
|
+
background_tasks: BackgroundTasks,
|
|
105
|
+
) -> None:
|
|
106
|
+
validated_user_id = validate_user_id(user_id=target_user_id)
|
|
107
|
+
await ban_user(
|
|
108
|
+
actor_id=actor_id.root,
|
|
109
|
+
user_id=validated_user_id.root,
|
|
110
|
+
category=moderation_reason.category,
|
|
111
|
+
reason=moderation_reason.reason,
|
|
112
|
+
)
|
|
113
|
+
background_tasks.add_task(auth.revoke_refresh_tokens, validated_user_id.root)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@router.put("/users/{target_user_id}/mute", status_code=status.HTTP_204_NO_CONTENT)
|
|
117
|
+
async def put_user_mute(actor_id: RequiredUserID, target_user_id: str, moderation_reason: ModerationReason) -> None:
|
|
118
|
+
validated_user_id = validate_user_id(user_id=target_user_id)
|
|
119
|
+
await mute_user(
|
|
120
|
+
actor_id=actor_id.root,
|
|
121
|
+
user_id=validated_user_id.root,
|
|
122
|
+
category=moderation_reason.category,
|
|
123
|
+
reason=moderation_reason.reason,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@router.put("/users/{target_user_id}/warn", status_code=status.HTTP_204_NO_CONTENT)
|
|
128
|
+
async def put_user_warn(actor_id: RequiredUserID, target_user_id: str, moderation_reason: ModerationReason) -> None:
|
|
129
|
+
validated_user_id = validate_user_id(user_id=target_user_id)
|
|
130
|
+
await warn_user(
|
|
131
|
+
actor_id=actor_id.root,
|
|
132
|
+
user_id=validated_user_id.root,
|
|
133
|
+
category=moderation_reason.category,
|
|
134
|
+
reason=moderation_reason.reason,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@router.delete("/users/{target_user_id}/restriction", status_code=status.HTTP_204_NO_CONTENT)
|
|
139
|
+
async def delete_user_restriction(actor_id: RequiredUserID, target_user_id: str) -> None:
|
|
140
|
+
validated_user_id = validate_user_id(user_id=target_user_id)
|
|
141
|
+
await clear_user_restriction(actor_id=actor_id.root, user_id=validated_user_id.root)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@router.get("/users/{target_user_id}/restriction-history", response_model=PaginationResponse[RestrictionHistoryItem])
|
|
145
|
+
async def get_restriction_history(
|
|
146
|
+
request: Request,
|
|
147
|
+
target_user_id: str,
|
|
148
|
+
pagination_params: Annotated[TokenCursorQueryParams, Depends()],
|
|
149
|
+
) -> Any:
|
|
150
|
+
validated_user_id = validate_user_id(user_id=target_user_id)
|
|
151
|
+
history = await retrieve_restriction_history(
|
|
152
|
+
user_id=validated_user_id.root,
|
|
153
|
+
cursor=pagination_params.cursor,
|
|
154
|
+
limit=pagination_params.limit,
|
|
155
|
+
)
|
|
156
|
+
return paginate_cursor(
|
|
157
|
+
request,
|
|
158
|
+
history,
|
|
159
|
+
pagination_params.limit,
|
|
160
|
+
PaginationCursorType.CREATED_AT,
|
|
161
|
+
retrieved=pagination_params.retrieved,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@router.get("/reports", response_model=PaginationResponse[ReportItem])
|
|
166
|
+
async def get_reports(
|
|
167
|
+
request: Request,
|
|
168
|
+
report_type: Annotated[ReportType, Query(alias="type")],
|
|
169
|
+
pagination_params: Annotated[TokenCursorQueryParams, Depends()],
|
|
170
|
+
reporter_user_id: Annotated[str | None, Query()] = None,
|
|
171
|
+
target_id: Annotated[str | None, Query()] = None,
|
|
172
|
+
) -> Any:
|
|
173
|
+
validated_reporter_user_id = validate_optional_user_id(user_id=reporter_user_id)
|
|
174
|
+
reports = await retrieve_reports(
|
|
175
|
+
report_type=report_type,
|
|
176
|
+
reporter_id=validated_reporter_user_id.root if validated_reporter_user_id else None,
|
|
177
|
+
target_id=target_id,
|
|
178
|
+
cursor=pagination_params.cursor,
|
|
179
|
+
limit=pagination_params.limit,
|
|
180
|
+
)
|
|
181
|
+
return paginate_cursor(
|
|
182
|
+
request,
|
|
183
|
+
reports,
|
|
184
|
+
pagination_params.limit,
|
|
185
|
+
PaginationCursorType.CREATED_AT,
|
|
186
|
+
retrieved=pagination_params.retrieved,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@router.delete("/reports/user/{report_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
191
|
+
async def delete_user_report(report_id: int) -> None:
|
|
192
|
+
validated_report_id = validate_report_id(report_id=report_id)
|
|
193
|
+
await remove_user_report(report_id=validated_report_id.root)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@router.delete("/reports/post/{report_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
197
|
+
async def delete_post_report(report_id: int) -> None:
|
|
198
|
+
validated_report_id = validate_report_id(report_id=report_id)
|
|
199
|
+
await remove_post_report(report_id=validated_report_id.root)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@router.delete("/reports/comment/{report_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
203
|
+
async def delete_comment_report(report_id: int) -> None:
|
|
204
|
+
validated_report_id = validate_report_id(report_id=report_id)
|
|
205
|
+
await remove_comment_report(report_id=validated_report_id.root)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Annotated, Any
|
|
3
|
+
|
|
4
|
+
from fastapi.exceptions import RequestValidationError
|
|
5
|
+
from pydantic import BaseModel, Field, RootModel, ValidationError
|
|
6
|
+
|
|
7
|
+
from core_framework.api.users.shared.schemas import UserID, UserReference
|
|
8
|
+
from core_framework.domains.moderation import (
|
|
9
|
+
AppealDecision,
|
|
10
|
+
HistoryAction,
|
|
11
|
+
ModerationActionType,
|
|
12
|
+
ReportCategory,
|
|
13
|
+
ReportType,
|
|
14
|
+
RestrictionCategory,
|
|
15
|
+
RestrictionType,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ModerationReason(BaseModel):
|
|
20
|
+
category: RestrictionCategory
|
|
21
|
+
reason: Annotated[str, Field(..., min_length=3, max_length=250)]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class RestrictionHistoryItem(BaseModel):
|
|
25
|
+
user_id: UserID
|
|
26
|
+
action: HistoryAction
|
|
27
|
+
restriction_type: RestrictionType
|
|
28
|
+
category: RestrictionCategory
|
|
29
|
+
reason: str
|
|
30
|
+
actor: UserReference | None
|
|
31
|
+
created_at: datetime
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ModerationActionResponse(BaseModel):
|
|
35
|
+
id: int
|
|
36
|
+
actor: UserReference
|
|
37
|
+
target_user: UserReference | None
|
|
38
|
+
action_type: ModerationActionType
|
|
39
|
+
action_metadata: dict[str, Any]
|
|
40
|
+
created_at: datetime
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class AppealID(RootModel[int]):
|
|
44
|
+
root: Annotated[
|
|
45
|
+
int,
|
|
46
|
+
Field(
|
|
47
|
+
description="The ID of the appeal",
|
|
48
|
+
ge=1,
|
|
49
|
+
),
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class PreviewItem(BaseModel):
|
|
54
|
+
key: str
|
|
55
|
+
value: str
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ReportItem(BaseModel):
|
|
59
|
+
type: ReportType
|
|
60
|
+
id: int
|
|
61
|
+
reporter: UserReference
|
|
62
|
+
target_id: str
|
|
63
|
+
category: ReportCategory
|
|
64
|
+
reason: str
|
|
65
|
+
created_at: datetime
|
|
66
|
+
preview: list[PreviewItem]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ReportID(RootModel[int]):
|
|
70
|
+
root: Annotated[
|
|
71
|
+
int,
|
|
72
|
+
Field(
|
|
73
|
+
description="The ID of the report",
|
|
74
|
+
ge=1,
|
|
75
|
+
),
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class AppealDecisionRequest(BaseModel):
|
|
80
|
+
decision: AppealDecision
|
|
81
|
+
reason: Annotated[str, Field(..., min_length=3, max_length=250)]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class AppealResponse(BaseModel):
|
|
85
|
+
id: int
|
|
86
|
+
user: UserReference
|
|
87
|
+
justification: str
|
|
88
|
+
reviewer: UserReference | None
|
|
89
|
+
decision_reason: str | None
|
|
90
|
+
status: AppealDecision
|
|
91
|
+
created_at: datetime
|
|
92
|
+
updated_at: datetime
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def validate_appeal_id(*, appeal_id: int) -> AppealID:
|
|
96
|
+
try:
|
|
97
|
+
return AppealID(
|
|
98
|
+
root=appeal_id,
|
|
99
|
+
)
|
|
100
|
+
except ValidationError as e:
|
|
101
|
+
raise RequestValidationError(e.errors()) from e
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def validate_report_id(*, report_id: int) -> ReportID:
|
|
105
|
+
try:
|
|
106
|
+
return ReportID(
|
|
107
|
+
root=report_id
|
|
108
|
+
) # Note: Workaround for Pydantic's lack of support for Root Model in Path parameters
|
|
109
|
+
except ValidationError as e:
|
|
110
|
+
raise RequestValidationError(e.errors()) from e
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from typing import Annotated, Any
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, Depends, Request, status
|
|
4
|
+
from ulid import ULID
|
|
5
|
+
|
|
6
|
+
from core_framework.api.admin.posts.schemas import AdminPostResponse
|
|
7
|
+
from core_framework.application.posts.admin_service import (
|
|
8
|
+
remove_post,
|
|
9
|
+
restore_post,
|
|
10
|
+
retrieve_all_posts,
|
|
11
|
+
retrieve_post_by_id,
|
|
12
|
+
set_post_inactive,
|
|
13
|
+
)
|
|
14
|
+
from core_framework.core.pagination import (
|
|
15
|
+
PaginationCursorType,
|
|
16
|
+
PaginationResponse,
|
|
17
|
+
TokenCursorQueryParams,
|
|
18
|
+
paginate_cursor,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
router = APIRouter(
|
|
22
|
+
prefix="/posts",
|
|
23
|
+
tags=["posts - admin"],
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@router.get("", response_model=PaginationResponse[AdminPostResponse])
|
|
28
|
+
async def get_posts(
|
|
29
|
+
request: Request,
|
|
30
|
+
pagination_params: Annotated[TokenCursorQueryParams, Depends()],
|
|
31
|
+
) -> Any:
|
|
32
|
+
posts = await retrieve_all_posts(
|
|
33
|
+
cursor=pagination_params.cursor,
|
|
34
|
+
limit=pagination_params.limit,
|
|
35
|
+
)
|
|
36
|
+
return paginate_cursor(
|
|
37
|
+
request=request,
|
|
38
|
+
items=posts,
|
|
39
|
+
limit=pagination_params.limit,
|
|
40
|
+
cursor_field=PaginationCursorType.CREATED_AT,
|
|
41
|
+
retrieved=pagination_params.retrieved,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@router.get("/{post_id}", response_model=AdminPostResponse)
|
|
46
|
+
async def get_post(post_id: ULID) -> Any:
|
|
47
|
+
return await retrieve_post_by_id(post_id=str(post_id))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@router.put("/{post_id}/inactive", status_code=status.HTTP_204_NO_CONTENT)
|
|
51
|
+
async def put_post_inactive(post_id: ULID) -> None:
|
|
52
|
+
await set_post_inactive(post_id=str(post_id))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@router.delete("/{post_id}/inactive", status_code=status.HTTP_204_NO_CONTENT)
|
|
56
|
+
async def delete_post_inactive(post_id: ULID) -> None:
|
|
57
|
+
await restore_post(post_id=str(post_id))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@router.delete("/{post_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
61
|
+
async def delete_post(post_id: ULID) -> None:
|
|
62
|
+
await remove_post(post_id=str(post_id))
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Annotated
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
from ulid import ULID
|
|
6
|
+
|
|
7
|
+
from core_framework.api.posts.schemas import Hashtag, PostContent
|
|
8
|
+
from core_framework.api.users.shared.schemas import UserReference
|
|
9
|
+
from core_framework.domains.post import PostStatus, PostVisibility
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AdminPostStatsResponse(BaseModel):
|
|
13
|
+
like_count: Annotated[int, Field(ge=0)] = 0
|
|
14
|
+
comment_count: Annotated[int, Field(ge=0)] = 0
|
|
15
|
+
view_count: Annotated[int, Field(ge=0)] = 0
|
|
16
|
+
report_count: Annotated[int, Field(ge=0)] = 0
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AdminPostResponse(BaseModel):
|
|
20
|
+
id: ULID
|
|
21
|
+
author: UserReference
|
|
22
|
+
content: PostContent
|
|
23
|
+
hashtags: list[Hashtag]
|
|
24
|
+
visibility: PostVisibility
|
|
25
|
+
status: PostStatus
|
|
26
|
+
edited_count: Annotated[int, Field(ge=0)] = 0
|
|
27
|
+
stats: AdminPostStatsResponse
|
|
28
|
+
edited_at: datetime | None
|
|
29
|
+
created_at: datetime
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from fastapi import APIRouter, Depends
|
|
2
|
+
|
|
3
|
+
from core_framework.api.admin.comments.router import router as comments_router
|
|
4
|
+
from core_framework.api.admin.moderation.router import router as moderation_router
|
|
5
|
+
from core_framework.api.admin.posts.router import router as posts_router
|
|
6
|
+
from core_framework.api.admin.users.router import router as users_router
|
|
7
|
+
from core_framework.api.dependencies import check_admin_role
|
|
8
|
+
|
|
9
|
+
router = APIRouter(
|
|
10
|
+
prefix="/admin",
|
|
11
|
+
dependencies=[Depends(check_admin_role)],
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
router.include_router(users_router)
|
|
15
|
+
router.include_router(moderation_router)
|
|
16
|
+
router.include_router(posts_router)
|
|
17
|
+
router.include_router(comments_router)
|
|
File without changes
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
from typing import Annotated, Any
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, Depends, Query, Request, status
|
|
4
|
+
|
|
5
|
+
from core_framework.api.admin.users.schemas import (
|
|
6
|
+
AdminProfileUpdateRequest,
|
|
7
|
+
InternalNoteDetailResponse,
|
|
8
|
+
InternalNoteRequest,
|
|
9
|
+
InternalNoteResponse,
|
|
10
|
+
RoleChangeRequest,
|
|
11
|
+
UserChangeHistoryItem,
|
|
12
|
+
UserDetailResponse,
|
|
13
|
+
UsernameChangeRequest,
|
|
14
|
+
UserSearchQueryParams,
|
|
15
|
+
UserSummaryResponse,
|
|
16
|
+
validate_note_id,
|
|
17
|
+
)
|
|
18
|
+
from core_framework.api.dependencies import RequiredUserID
|
|
19
|
+
from core_framework.api.users.shared.schemas import validate_user_id
|
|
20
|
+
from core_framework.application.users.admin_service import (
|
|
21
|
+
add_user_note,
|
|
22
|
+
change_user_profile,
|
|
23
|
+
change_user_role,
|
|
24
|
+
change_user_username,
|
|
25
|
+
remove_user,
|
|
26
|
+
remove_user_note,
|
|
27
|
+
retrieve_user_detail,
|
|
28
|
+
retrieve_user_history,
|
|
29
|
+
retrieve_user_notes,
|
|
30
|
+
retrieve_users,
|
|
31
|
+
)
|
|
32
|
+
from core_framework.core.pagination import (
|
|
33
|
+
PaginationCursorType,
|
|
34
|
+
PaginationResponse,
|
|
35
|
+
TokenCursorQueryParams,
|
|
36
|
+
paginate_cursor,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
router = APIRouter(
|
|
40
|
+
prefix="/users",
|
|
41
|
+
tags=["users - admin"],
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@router.get("", response_model=PaginationResponse[UserSummaryResponse])
|
|
46
|
+
async def get_users(
|
|
47
|
+
request: Request,
|
|
48
|
+
pagination_params: Annotated[TokenCursorQueryParams, Depends()],
|
|
49
|
+
search_params: Annotated[UserSearchQueryParams, Query()],
|
|
50
|
+
) -> Any:
|
|
51
|
+
users = await retrieve_users(
|
|
52
|
+
cursor=pagination_params.cursor,
|
|
53
|
+
limit=pagination_params.limit,
|
|
54
|
+
username=search_params.username,
|
|
55
|
+
role=search_params.role,
|
|
56
|
+
)
|
|
57
|
+
return paginate_cursor(
|
|
58
|
+
request,
|
|
59
|
+
users,
|
|
60
|
+
pagination_params.limit,
|
|
61
|
+
PaginationCursorType.CREATED_AT,
|
|
62
|
+
retrieved=pagination_params.retrieved,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@router.get("/{user_id}", response_model=UserDetailResponse)
|
|
67
|
+
async def get_user_detail(user_id: str) -> Any:
|
|
68
|
+
validated_user_id = validate_user_id(user_id=user_id)
|
|
69
|
+
return await retrieve_user_detail(user_id=validated_user_id.root)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@router.patch("/{user_id}/username", status_code=status.HTTP_204_NO_CONTENT)
|
|
73
|
+
async def patch_user_username(
|
|
74
|
+
actor_id: RequiredUserID,
|
|
75
|
+
user_id: str,
|
|
76
|
+
request_body: UsernameChangeRequest,
|
|
77
|
+
) -> None:
|
|
78
|
+
validated_user_id = validate_user_id(user_id=user_id)
|
|
79
|
+
await change_user_username(
|
|
80
|
+
actor_id=actor_id.root,
|
|
81
|
+
user_id=validated_user_id.root,
|
|
82
|
+
new_username=request_body.username.root,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@router.patch("/{user_id}/role", status_code=status.HTTP_204_NO_CONTENT)
|
|
87
|
+
async def patch_user_role(
|
|
88
|
+
actor_id: RequiredUserID,
|
|
89
|
+
user_id: str,
|
|
90
|
+
request_body: RoleChangeRequest,
|
|
91
|
+
) -> None:
|
|
92
|
+
validated_user_id = validate_user_id(user_id=user_id)
|
|
93
|
+
await change_user_role(
|
|
94
|
+
actor_id=actor_id.root,
|
|
95
|
+
user_id=validated_user_id.root,
|
|
96
|
+
new_role=request_body.role,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@router.patch("/{user_id}/profile", status_code=status.HTTP_204_NO_CONTENT)
|
|
101
|
+
async def patch_user_profile(
|
|
102
|
+
actor_id: RequiredUserID,
|
|
103
|
+
user_id: str,
|
|
104
|
+
request_body: AdminProfileUpdateRequest,
|
|
105
|
+
) -> None:
|
|
106
|
+
validated_user_id = validate_user_id(user_id=user_id)
|
|
107
|
+
validated_update_request = request_body.model_dump(mode="json", exclude_unset=True)
|
|
108
|
+
await change_user_profile(
|
|
109
|
+
actor_id=actor_id.root,
|
|
110
|
+
user_id=validated_user_id.root,
|
|
111
|
+
validated_update_request=validated_update_request,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@router.get("/{user_id}/change-history", response_model=PaginationResponse[UserChangeHistoryItem])
|
|
116
|
+
async def get_user_history(
|
|
117
|
+
request: Request,
|
|
118
|
+
user_id: str,
|
|
119
|
+
pagination_params: Annotated[TokenCursorQueryParams, Depends()],
|
|
120
|
+
) -> Any:
|
|
121
|
+
validated_user_id = validate_user_id(user_id=user_id)
|
|
122
|
+
history = await retrieve_user_history(
|
|
123
|
+
user_id=validated_user_id.root,
|
|
124
|
+
cursor=pagination_params.cursor,
|
|
125
|
+
limit=pagination_params.limit,
|
|
126
|
+
)
|
|
127
|
+
return paginate_cursor(
|
|
128
|
+
request,
|
|
129
|
+
history,
|
|
130
|
+
pagination_params.limit,
|
|
131
|
+
PaginationCursorType.CREATED_AT,
|
|
132
|
+
retrieved=pagination_params.retrieved,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@router.get("/{user_id}/notes", response_model=PaginationResponse[InternalNoteDetailResponse])
|
|
137
|
+
async def get_user_notes(
|
|
138
|
+
request: Request,
|
|
139
|
+
user_id: str,
|
|
140
|
+
pagination_params: Annotated[TokenCursorQueryParams, Depends()],
|
|
141
|
+
) -> Any:
|
|
142
|
+
validated_user_id = validate_user_id(user_id=user_id)
|
|
143
|
+
notes = await retrieve_user_notes(
|
|
144
|
+
user_id=validated_user_id.root,
|
|
145
|
+
cursor=pagination_params.cursor,
|
|
146
|
+
limit=pagination_params.limit,
|
|
147
|
+
)
|
|
148
|
+
return paginate_cursor(
|
|
149
|
+
request,
|
|
150
|
+
notes,
|
|
151
|
+
pagination_params.limit,
|
|
152
|
+
PaginationCursorType.CREATED_AT,
|
|
153
|
+
retrieved=pagination_params.retrieved,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@router.post("/{user_id}/notes", response_model=InternalNoteResponse, status_code=status.HTTP_201_CREATED)
|
|
158
|
+
async def post_user_note(
|
|
159
|
+
actor_id: RequiredUserID,
|
|
160
|
+
user_id: str,
|
|
161
|
+
request_body: InternalNoteRequest,
|
|
162
|
+
) -> Any:
|
|
163
|
+
validated_user_id = validate_user_id(user_id=user_id)
|
|
164
|
+
return await add_user_note(
|
|
165
|
+
actor_id=actor_id.root,
|
|
166
|
+
user_id=validated_user_id.root,
|
|
167
|
+
content=request_body.content,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@router.delete("/{user_id}/notes/{note_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
172
|
+
async def delete_user_note(actor_id: RequiredUserID, user_id: str, note_id: int) -> None:
|
|
173
|
+
validated_user_id = validate_user_id(user_id=user_id)
|
|
174
|
+
validated_note_id = validate_note_id(note_id=note_id)
|
|
175
|
+
await remove_user_note(actor_id=actor_id.root, user_id=validated_user_id.root, note_id=validated_note_id.root)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
179
|
+
async def delete_user(actor_id: RequiredUserID, user_id: str) -> None:
|
|
180
|
+
validated_user_id = validate_user_id(user_id=user_id)
|
|
181
|
+
await remove_user(actor_id=actor_id.root, user_id=validated_user_id.root)
|