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,947 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Any, Literal
|
|
3
|
+
|
|
4
|
+
from asyncpg import Record
|
|
5
|
+
|
|
6
|
+
from core_framework.core.database import Postgres
|
|
7
|
+
from core_framework.domains.comment.constants import REDACTED_CONTENT_PLACEHOLDER
|
|
8
|
+
from core_framework.domains.comment.enums import CommentStatus, CommentSubjectType
|
|
9
|
+
from core_framework.domains.comment.exceptions import (
|
|
10
|
+
BaseCommentException,
|
|
11
|
+
CommentEditLimitReachedException,
|
|
12
|
+
CommentNotFoundException,
|
|
13
|
+
CommentUpdateNotFoundException,
|
|
14
|
+
MaxReplyDepthException,
|
|
15
|
+
ParentCommentNotFoundException,
|
|
16
|
+
)
|
|
17
|
+
from core_framework.domains.comment.models import Comment, CommentPreview, CommentStats, CommentWithMetadata
|
|
18
|
+
from core_framework.domains.user import REDACTED_AUTHOR_ID
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CommentRepository:
|
|
22
|
+
def __init__(self, write_database: Postgres, read_database: Postgres, strong_read_database: Postgres):
|
|
23
|
+
self.write_database = write_database
|
|
24
|
+
self.read_database = read_database
|
|
25
|
+
self.strong_read_database = strong_read_database
|
|
26
|
+
|
|
27
|
+
async def insert_comment(
|
|
28
|
+
self,
|
|
29
|
+
*,
|
|
30
|
+
comment_id: str,
|
|
31
|
+
author_id: str,
|
|
32
|
+
content: str,
|
|
33
|
+
subject_type: CommentSubjectType,
|
|
34
|
+
subject_id: str,
|
|
35
|
+
) -> None:
|
|
36
|
+
insert_root_comment_query = """
|
|
37
|
+
insert into "comment".comments (
|
|
38
|
+
id,
|
|
39
|
+
author_id,
|
|
40
|
+
content,
|
|
41
|
+
subject_type,
|
|
42
|
+
subject_id,
|
|
43
|
+
path,
|
|
44
|
+
level
|
|
45
|
+
)
|
|
46
|
+
values (
|
|
47
|
+
$3,
|
|
48
|
+
$4,
|
|
49
|
+
$5,
|
|
50
|
+
$1::"comment".comment_subject_type,
|
|
51
|
+
$2,
|
|
52
|
+
text2ltree($6::text),
|
|
53
|
+
0
|
|
54
|
+
)
|
|
55
|
+
"""
|
|
56
|
+
insert_comment_stats_query = """
|
|
57
|
+
insert into "comment".comment_stats (comment_id)
|
|
58
|
+
values ($1)
|
|
59
|
+
"""
|
|
60
|
+
async with self.write_database.get_transaction() as connection:
|
|
61
|
+
await connection.execute(
|
|
62
|
+
insert_root_comment_query,
|
|
63
|
+
subject_type.value,
|
|
64
|
+
subject_id,
|
|
65
|
+
comment_id,
|
|
66
|
+
author_id,
|
|
67
|
+
content,
|
|
68
|
+
comment_id,
|
|
69
|
+
)
|
|
70
|
+
await connection.execute(insert_comment_stats_query, comment_id)
|
|
71
|
+
|
|
72
|
+
async def insert_reply(
|
|
73
|
+
self,
|
|
74
|
+
*,
|
|
75
|
+
comment_id: str,
|
|
76
|
+
author_id: str,
|
|
77
|
+
content: str,
|
|
78
|
+
parent_comment_id: str,
|
|
79
|
+
max_reply_level: int,
|
|
80
|
+
) -> None:
|
|
81
|
+
insert_reply_query = """
|
|
82
|
+
with parent_check as (
|
|
83
|
+
select id, status, level, path, subject_type, subject_id
|
|
84
|
+
from "comment".comments
|
|
85
|
+
where id = $1
|
|
86
|
+
),
|
|
87
|
+
outcome as (
|
|
88
|
+
select
|
|
89
|
+
case
|
|
90
|
+
when not exists (select 1 from parent_check) then 'parent_not_found'
|
|
91
|
+
when (select status from parent_check limit 1) != 'active' then 'parent_not_found'
|
|
92
|
+
when (select level from parent_check limit 1) >= $2 then 'max_depth'
|
|
93
|
+
else 'ok'
|
|
94
|
+
end as result
|
|
95
|
+
),
|
|
96
|
+
valid_parent as (
|
|
97
|
+
select path, level, subject_type, subject_id
|
|
98
|
+
from parent_check
|
|
99
|
+
where status = 'active' and level < $2
|
|
100
|
+
),
|
|
101
|
+
insert_result as (
|
|
102
|
+
insert into "comment".comments (
|
|
103
|
+
id,
|
|
104
|
+
author_id,
|
|
105
|
+
content,
|
|
106
|
+
subject_type,
|
|
107
|
+
subject_id,
|
|
108
|
+
parent_comment_id,
|
|
109
|
+
path,
|
|
110
|
+
level
|
|
111
|
+
)
|
|
112
|
+
select
|
|
113
|
+
$3,
|
|
114
|
+
$4,
|
|
115
|
+
$5,
|
|
116
|
+
p.subject_type,
|
|
117
|
+
p.subject_id,
|
|
118
|
+
$1,
|
|
119
|
+
p.path || $6::text,
|
|
120
|
+
p.level + 1
|
|
121
|
+
from valid_parent p
|
|
122
|
+
returning id
|
|
123
|
+
)
|
|
124
|
+
select
|
|
125
|
+
(select id from insert_result limit 1) as inserted_id,
|
|
126
|
+
coalesce(
|
|
127
|
+
(select 'ok' from insert_result limit 1),
|
|
128
|
+
(select result from outcome)
|
|
129
|
+
) as outcome
|
|
130
|
+
"""
|
|
131
|
+
insert_comment_stats_query = """
|
|
132
|
+
insert into "comment".comment_stats (comment_id)
|
|
133
|
+
values ($1)
|
|
134
|
+
"""
|
|
135
|
+
async with self.write_database.get_transaction() as connection:
|
|
136
|
+
row = await connection.fetchrow(
|
|
137
|
+
insert_reply_query,
|
|
138
|
+
parent_comment_id,
|
|
139
|
+
max_reply_level,
|
|
140
|
+
comment_id,
|
|
141
|
+
author_id,
|
|
142
|
+
content,
|
|
143
|
+
comment_id,
|
|
144
|
+
)
|
|
145
|
+
match row["outcome"]:
|
|
146
|
+
case "ok":
|
|
147
|
+
await connection.execute(insert_comment_stats_query, comment_id)
|
|
148
|
+
return
|
|
149
|
+
case "max_depth":
|
|
150
|
+
raise MaxReplyDepthException()
|
|
151
|
+
case "parent_not_found":
|
|
152
|
+
raise ParentCommentNotFoundException()
|
|
153
|
+
case _:
|
|
154
|
+
raise BaseCommentException("Unexpected reply insert outcome")
|
|
155
|
+
|
|
156
|
+
async def insert_user_restriction_lookup(
|
|
157
|
+
self,
|
|
158
|
+
*,
|
|
159
|
+
user_id: str,
|
|
160
|
+
restriction_type: Literal["muted", "banned"],
|
|
161
|
+
) -> None:
|
|
162
|
+
query = """
|
|
163
|
+
merge into "comment".user_restrictions_lookup as target
|
|
164
|
+
using (values ($1, $2)) as source(user_id, restriction_type)
|
|
165
|
+
on target.user_id = source.user_id
|
|
166
|
+
when matched then
|
|
167
|
+
update set restriction_type = source.restriction_type
|
|
168
|
+
when not matched then
|
|
169
|
+
insert (user_id, restriction_type)
|
|
170
|
+
values (source.user_id, source.restriction_type)
|
|
171
|
+
"""
|
|
172
|
+
async with self.write_database.get_connection() as connection:
|
|
173
|
+
await connection.execute(query, user_id, restriction_type)
|
|
174
|
+
|
|
175
|
+
async def delete_user_restriction_lookup(self, *, user_id: str) -> None:
|
|
176
|
+
query = """
|
|
177
|
+
delete from "comment".user_restrictions_lookup
|
|
178
|
+
where true
|
|
179
|
+
and user_id = $1
|
|
180
|
+
"""
|
|
181
|
+
async with self.write_database.get_connection() as connection:
|
|
182
|
+
await connection.execute(query, user_id)
|
|
183
|
+
|
|
184
|
+
async def insert_user_block_lookup(self, *, blocker_id: str, blocked_id: str) -> None:
|
|
185
|
+
query = """
|
|
186
|
+
merge into "comment".user_blocks_lookup as target
|
|
187
|
+
using (values ($1, $2)) as source(blocker_id, blocked_id)
|
|
188
|
+
on target.blocker_id = source.blocker_id
|
|
189
|
+
and target.blocked_id = source.blocked_id
|
|
190
|
+
when not matched then
|
|
191
|
+
insert (blocker_id, blocked_id)
|
|
192
|
+
values (source.blocker_id, source.blocked_id)
|
|
193
|
+
"""
|
|
194
|
+
async with self.write_database.get_connection() as connection:
|
|
195
|
+
await connection.execute(query, blocker_id, blocked_id)
|
|
196
|
+
|
|
197
|
+
async def delete_user_block_lookup(self, *, blocker_id: str, blocked_id: str) -> None:
|
|
198
|
+
query = """
|
|
199
|
+
delete from "comment".user_blocks_lookup
|
|
200
|
+
where true
|
|
201
|
+
and blocker_id = $1
|
|
202
|
+
and blocked_id = $2
|
|
203
|
+
"""
|
|
204
|
+
async with self.write_database.get_connection() as connection:
|
|
205
|
+
await connection.execute(query, blocker_id, blocked_id)
|
|
206
|
+
|
|
207
|
+
async def delete_user(self, *, user_id: str) -> None:
|
|
208
|
+
redact_comments_query = """
|
|
209
|
+
update "comment".comments
|
|
210
|
+
set
|
|
211
|
+
content = $2,
|
|
212
|
+
author_id = $3,
|
|
213
|
+
updated_at = now()
|
|
214
|
+
where true
|
|
215
|
+
and author_id = $1
|
|
216
|
+
"""
|
|
217
|
+
delete_restrictions_query = """
|
|
218
|
+
delete from "comment".user_restrictions_lookup
|
|
219
|
+
where true
|
|
220
|
+
and user_id = $1
|
|
221
|
+
"""
|
|
222
|
+
delete_blocks_query = """
|
|
223
|
+
delete from "comment".user_blocks_lookup
|
|
224
|
+
where true
|
|
225
|
+
and (
|
|
226
|
+
blocker_id = $1
|
|
227
|
+
or blocked_id = $1
|
|
228
|
+
)
|
|
229
|
+
"""
|
|
230
|
+
async with self.write_database.get_transaction() as connection:
|
|
231
|
+
await connection.execute(
|
|
232
|
+
redact_comments_query,
|
|
233
|
+
user_id,
|
|
234
|
+
REDACTED_CONTENT_PLACEHOLDER,
|
|
235
|
+
REDACTED_AUTHOR_ID,
|
|
236
|
+
)
|
|
237
|
+
await connection.execute(delete_restrictions_query, user_id)
|
|
238
|
+
await connection.execute(delete_blocks_query, user_id)
|
|
239
|
+
|
|
240
|
+
async def insert_comment_like(self, *, comment_id: str, liker_id: str) -> None:
|
|
241
|
+
query = """
|
|
242
|
+
insert into "comment".comment_likes (comment_id, liker_id)
|
|
243
|
+
select
|
|
244
|
+
$1::varchar,
|
|
245
|
+
$2::varchar
|
|
246
|
+
where exists (
|
|
247
|
+
select 1
|
|
248
|
+
from "comment".comments
|
|
249
|
+
where true
|
|
250
|
+
and id = $1::varchar
|
|
251
|
+
and status = 'active'::"comment".comment_status
|
|
252
|
+
)
|
|
253
|
+
on conflict (comment_id, liker_id) do nothing
|
|
254
|
+
"""
|
|
255
|
+
async with self.write_database.get_connection() as connection:
|
|
256
|
+
await connection.execute(query, comment_id, liker_id)
|
|
257
|
+
|
|
258
|
+
async def delete_comment_like(self, *, comment_id: str, liker_id: str) -> None:
|
|
259
|
+
query = """
|
|
260
|
+
delete from "comment".comment_likes
|
|
261
|
+
where true
|
|
262
|
+
and comment_id = $1
|
|
263
|
+
and liker_id = $2
|
|
264
|
+
"""
|
|
265
|
+
async with self.write_database.get_connection() as connection:
|
|
266
|
+
await connection.execute(query, comment_id, liker_id)
|
|
267
|
+
|
|
268
|
+
async def update_comment(
|
|
269
|
+
self,
|
|
270
|
+
*,
|
|
271
|
+
comment_id: str,
|
|
272
|
+
author_id: str,
|
|
273
|
+
comment_updates: dict[str, Any],
|
|
274
|
+
max_edit_count: int,
|
|
275
|
+
) -> None:
|
|
276
|
+
content = comment_updates.get("content")
|
|
277
|
+
if content is None:
|
|
278
|
+
return
|
|
279
|
+
|
|
280
|
+
query = """
|
|
281
|
+
with comment_check as (
|
|
282
|
+
select
|
|
283
|
+
id,
|
|
284
|
+
edited_count
|
|
285
|
+
from "comment".comments
|
|
286
|
+
where true
|
|
287
|
+
and id = $1
|
|
288
|
+
and author_id = $2
|
|
289
|
+
and status = 'active'::"comment".comment_status
|
|
290
|
+
),
|
|
291
|
+
update_result as (
|
|
292
|
+
update "comment".comments c
|
|
293
|
+
set
|
|
294
|
+
content = $3,
|
|
295
|
+
edited_count = case when c.content is distinct from $3
|
|
296
|
+
then c.edited_count + 1 else c.edited_count end,
|
|
297
|
+
edited_at = case when c.content is distinct from $3
|
|
298
|
+
then now() else c.edited_at end,
|
|
299
|
+
updated_at = now()
|
|
300
|
+
from comment_check cc
|
|
301
|
+
where true
|
|
302
|
+
and c.id = cc.id
|
|
303
|
+
and cc.edited_count < $4
|
|
304
|
+
returning 1
|
|
305
|
+
)
|
|
306
|
+
select
|
|
307
|
+
(select count(*) from comment_check) as found,
|
|
308
|
+
(select count(*) from update_result) as rows_updated
|
|
309
|
+
"""
|
|
310
|
+
async with self.write_database.get_connection() as connection:
|
|
311
|
+
row = await connection.fetchrow(
|
|
312
|
+
query,
|
|
313
|
+
comment_id,
|
|
314
|
+
author_id,
|
|
315
|
+
content,
|
|
316
|
+
max_edit_count,
|
|
317
|
+
)
|
|
318
|
+
if row["found"] == 0:
|
|
319
|
+
raise CommentUpdateNotFoundException()
|
|
320
|
+
if row["rows_updated"] == 0:
|
|
321
|
+
raise CommentEditLimitReachedException()
|
|
322
|
+
|
|
323
|
+
async def update_comment_status_by_id_and_author(
|
|
324
|
+
self,
|
|
325
|
+
*,
|
|
326
|
+
comment_id: str,
|
|
327
|
+
author_id: str,
|
|
328
|
+
status: CommentStatus,
|
|
329
|
+
) -> str | None:
|
|
330
|
+
query = """
|
|
331
|
+
with upd as (
|
|
332
|
+
update "comment".comments
|
|
333
|
+
set
|
|
334
|
+
status = $3::"comment".comment_status,
|
|
335
|
+
updated_at = now()
|
|
336
|
+
where true
|
|
337
|
+
and id = $1
|
|
338
|
+
and author_id = $2
|
|
339
|
+
and status != $3::"comment".comment_status
|
|
340
|
+
returning id, parent_comment_id
|
|
341
|
+
),
|
|
342
|
+
removed_dirty as (
|
|
343
|
+
delete from "comment".comment_stats_dirty d
|
|
344
|
+
using upd
|
|
345
|
+
where d.comment_id = upd.id
|
|
346
|
+
)
|
|
347
|
+
select parent_comment_id from upd
|
|
348
|
+
"""
|
|
349
|
+
async with self.write_database.get_connection() as connection:
|
|
350
|
+
row = await connection.fetchrow(query, comment_id, author_id, status.value)
|
|
351
|
+
return row["parent_comment_id"] if row else None
|
|
352
|
+
|
|
353
|
+
async def update_comment_status_by_id(
|
|
354
|
+
self,
|
|
355
|
+
*,
|
|
356
|
+
comment_id: str,
|
|
357
|
+
status: CommentStatus,
|
|
358
|
+
) -> str | None:
|
|
359
|
+
query = """
|
|
360
|
+
with upd as (
|
|
361
|
+
update "comment".comments
|
|
362
|
+
set
|
|
363
|
+
status = $2::"comment".comment_status,
|
|
364
|
+
updated_at = now()
|
|
365
|
+
where id = $1
|
|
366
|
+
returning id, parent_comment_id
|
|
367
|
+
),
|
|
368
|
+
removed_dirty as (
|
|
369
|
+
delete from "comment".comment_stats_dirty d
|
|
370
|
+
using upd
|
|
371
|
+
where d.comment_id = upd.id
|
|
372
|
+
)
|
|
373
|
+
select parent_comment_id from upd
|
|
374
|
+
"""
|
|
375
|
+
async with self.write_database.get_connection() as connection:
|
|
376
|
+
row = await connection.fetchrow(query, comment_id, status.value)
|
|
377
|
+
if row is None:
|
|
378
|
+
raise CommentNotFoundException()
|
|
379
|
+
return row["parent_comment_id"]
|
|
380
|
+
|
|
381
|
+
async def select_comment_ids_by_subject_strong(
|
|
382
|
+
self,
|
|
383
|
+
*,
|
|
384
|
+
subject_type: CommentSubjectType,
|
|
385
|
+
subject_id: str,
|
|
386
|
+
) -> set[str]:
|
|
387
|
+
query = """
|
|
388
|
+
select id
|
|
389
|
+
from "comment".comments
|
|
390
|
+
where true
|
|
391
|
+
and subject_type = $1::"comment".comment_subject_type
|
|
392
|
+
and subject_id = $2
|
|
393
|
+
"""
|
|
394
|
+
async with self.strong_read_database.get_connection() as connection:
|
|
395
|
+
rows = await connection.fetch(query, subject_type.value, subject_id)
|
|
396
|
+
return {row["id"] for row in rows}
|
|
397
|
+
|
|
398
|
+
async def select_active_comment_count_for_post(self, *, post_id: str) -> int:
|
|
399
|
+
# Active rows only if no soft-deleted ancestor on this post (a.path @> c.path).
|
|
400
|
+
query = """
|
|
401
|
+
select count(*)
|
|
402
|
+
from "comment".comments c
|
|
403
|
+
where true
|
|
404
|
+
and c.subject_type = 'post'::"comment".comment_subject_type
|
|
405
|
+
and c.subject_id = $1
|
|
406
|
+
and c.status = 'active'::"comment".comment_status
|
|
407
|
+
and not exists (
|
|
408
|
+
select 1
|
|
409
|
+
from "comment".comments a
|
|
410
|
+
where true
|
|
411
|
+
and a.subject_type = 'post'::"comment".comment_subject_type
|
|
412
|
+
and a.subject_id = $1
|
|
413
|
+
and a.path @> c.path
|
|
414
|
+
and a.status = 'deleted'::"comment".comment_status
|
|
415
|
+
)
|
|
416
|
+
"""
|
|
417
|
+
async with self.read_database.get_connection() as connection:
|
|
418
|
+
value = await connection.fetchval(query, post_id)
|
|
419
|
+
return int(value or 0)
|
|
420
|
+
|
|
421
|
+
async def select_comment_subtree_ids_strong(self, *, root_comment_id: str) -> set[str]:
|
|
422
|
+
query = """
|
|
423
|
+
with root_comment as (
|
|
424
|
+
select subject_type, subject_id, path
|
|
425
|
+
from "comment".comments
|
|
426
|
+
where id = $1
|
|
427
|
+
)
|
|
428
|
+
select c.id
|
|
429
|
+
from "comment".comments c
|
|
430
|
+
join root_comment r
|
|
431
|
+
on c.subject_type = r.subject_type
|
|
432
|
+
and c.subject_id = r.subject_id
|
|
433
|
+
and c.path <@ r.path
|
|
434
|
+
"""
|
|
435
|
+
async with self.strong_read_database.get_connection() as connection:
|
|
436
|
+
rows = await connection.fetch(query, root_comment_id)
|
|
437
|
+
return {row["id"] for row in rows}
|
|
438
|
+
|
|
439
|
+
async def select_parent_comment_id_strong(self, *, comment_id: str) -> str | None:
|
|
440
|
+
query = """
|
|
441
|
+
select parent_comment_id
|
|
442
|
+
from "comment".comments
|
|
443
|
+
where id = $1
|
|
444
|
+
"""
|
|
445
|
+
async with self.strong_read_database.get_connection() as connection:
|
|
446
|
+
return await connection.fetchval(query, comment_id)
|
|
447
|
+
|
|
448
|
+
async def delete_comment_subtree(self, *, root_comment_id: str) -> None:
|
|
449
|
+
query = """
|
|
450
|
+
delete from "comment".comments
|
|
451
|
+
where id = $1
|
|
452
|
+
"""
|
|
453
|
+
async with self.write_database.get_connection() as connection:
|
|
454
|
+
await connection.execute(query, root_comment_id)
|
|
455
|
+
|
|
456
|
+
async def delete_comments_by_subject(
|
|
457
|
+
self,
|
|
458
|
+
*,
|
|
459
|
+
subject_type: CommentSubjectType,
|
|
460
|
+
subject_id: str,
|
|
461
|
+
) -> None:
|
|
462
|
+
query = """
|
|
463
|
+
delete from "comment".comments
|
|
464
|
+
where true
|
|
465
|
+
and subject_type = $1::"comment".comment_subject_type
|
|
466
|
+
and subject_id = $2
|
|
467
|
+
"""
|
|
468
|
+
async with self.write_database.get_connection() as connection:
|
|
469
|
+
await connection.execute(query, subject_type.value, subject_id)
|
|
470
|
+
|
|
471
|
+
async def select_comment_ids_liked_by_user(self, *, liker_id: str, comment_ids: set[str]) -> list[str]:
|
|
472
|
+
if not comment_ids:
|
|
473
|
+
return []
|
|
474
|
+
query = """
|
|
475
|
+
select comment_id
|
|
476
|
+
from "comment".comment_likes
|
|
477
|
+
where true
|
|
478
|
+
and liker_id = $1
|
|
479
|
+
and comment_id = any($2::varchar[])
|
|
480
|
+
"""
|
|
481
|
+
async with self.read_database.get_connection() as connection:
|
|
482
|
+
rows = await connection.fetch(query, liker_id, list(comment_ids))
|
|
483
|
+
return [row["comment_id"] for row in rows]
|
|
484
|
+
|
|
485
|
+
async def select_comment_content_mapping(self, *, comment_ids: set[str]) -> dict[str, str]:
|
|
486
|
+
if not comment_ids:
|
|
487
|
+
return {}
|
|
488
|
+
query = """
|
|
489
|
+
select id, content
|
|
490
|
+
from "comment".comments
|
|
491
|
+
where id = any($1::varchar[])
|
|
492
|
+
"""
|
|
493
|
+
async with self.read_database.get_connection() as connection:
|
|
494
|
+
result = await connection.fetch(query, list(comment_ids))
|
|
495
|
+
return {row["id"]: row["content"] or "" for row in result}
|
|
496
|
+
|
|
497
|
+
async def select_comment_stats_mapping(self, *, comment_ids: set[str]) -> dict[str, CommentStats]:
|
|
498
|
+
if not comment_ids:
|
|
499
|
+
return {}
|
|
500
|
+
query = """
|
|
501
|
+
select
|
|
502
|
+
comment_id,
|
|
503
|
+
like_count,
|
|
504
|
+
reply_count,
|
|
505
|
+
report_count
|
|
506
|
+
from "comment".comment_stats
|
|
507
|
+
where comment_id = any($1::varchar[])
|
|
508
|
+
"""
|
|
509
|
+
async with self.read_database.get_connection() as connection:
|
|
510
|
+
result = await connection.fetch(query, list(comment_ids))
|
|
511
|
+
return {
|
|
512
|
+
row["comment_id"]: CommentStats(
|
|
513
|
+
like_count=row["like_count"],
|
|
514
|
+
reply_count=row["reply_count"],
|
|
515
|
+
report_count=row["report_count"],
|
|
516
|
+
)
|
|
517
|
+
for row in result
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
async def insert_comment_stats_dirty(self, *, comment_id: str) -> None:
|
|
521
|
+
query = """
|
|
522
|
+
insert into "comment".comment_stats_dirty (comment_id)
|
|
523
|
+
values ($1)
|
|
524
|
+
on conflict (comment_id) do nothing
|
|
525
|
+
"""
|
|
526
|
+
async with self.write_database.get_connection() as connection:
|
|
527
|
+
await connection.execute(query, comment_id)
|
|
528
|
+
|
|
529
|
+
async def claim_comment_ids_dirty(self) -> list[str]:
|
|
530
|
+
query = """
|
|
531
|
+
delete from "comment".comment_stats_dirty
|
|
532
|
+
returning comment_id
|
|
533
|
+
"""
|
|
534
|
+
async with self.write_database.get_transaction() as connection:
|
|
535
|
+
rows = await connection.fetch(query)
|
|
536
|
+
return [row["comment_id"] for row in rows]
|
|
537
|
+
|
|
538
|
+
async def delete_comment_stats_dirty(self, *, comment_ids: set[str]) -> None:
|
|
539
|
+
if not comment_ids:
|
|
540
|
+
return
|
|
541
|
+
query = """
|
|
542
|
+
delete from "comment".comment_stats_dirty
|
|
543
|
+
where comment_id = any($1::varchar[])
|
|
544
|
+
"""
|
|
545
|
+
async with self.write_database.get_connection() as connection:
|
|
546
|
+
await connection.execute(query, list(comment_ids))
|
|
547
|
+
|
|
548
|
+
async def update_comment_stats(
|
|
549
|
+
self,
|
|
550
|
+
*,
|
|
551
|
+
comment_id: str,
|
|
552
|
+
like_count: int,
|
|
553
|
+
reply_count: int,
|
|
554
|
+
report_count: int,
|
|
555
|
+
) -> None:
|
|
556
|
+
query = """
|
|
557
|
+
update "comment".comment_stats
|
|
558
|
+
set
|
|
559
|
+
like_count = $2,
|
|
560
|
+
reply_count = $3,
|
|
561
|
+
report_count = $4,
|
|
562
|
+
updated_at = now()
|
|
563
|
+
where comment_id = $1
|
|
564
|
+
"""
|
|
565
|
+
async with self.write_database.get_connection() as connection:
|
|
566
|
+
await connection.execute(
|
|
567
|
+
query,
|
|
568
|
+
comment_id,
|
|
569
|
+
like_count,
|
|
570
|
+
reply_count,
|
|
571
|
+
report_count,
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
async def select_comment_like_count(self, *, comment_id: str) -> int:
|
|
575
|
+
query = """
|
|
576
|
+
select count(*)
|
|
577
|
+
from "comment".comment_likes
|
|
578
|
+
where comment_id = $1
|
|
579
|
+
"""
|
|
580
|
+
async with self.read_database.get_connection() as connection:
|
|
581
|
+
return await connection.fetchval(query, comment_id) or 0
|
|
582
|
+
|
|
583
|
+
async def select_comment_reply_count(self, *, comment_id: str) -> int:
|
|
584
|
+
query = """
|
|
585
|
+
select count(*)
|
|
586
|
+
from "comment".comments
|
|
587
|
+
where true
|
|
588
|
+
and parent_comment_id = $1
|
|
589
|
+
and status = 'active'::"comment".comment_status
|
|
590
|
+
"""
|
|
591
|
+
async with self.read_database.get_connection() as connection:
|
|
592
|
+
return await connection.fetchval(query, comment_id) or 0
|
|
593
|
+
|
|
594
|
+
async def select_comments_with_metadata(
|
|
595
|
+
self,
|
|
596
|
+
*,
|
|
597
|
+
cursor: datetime,
|
|
598
|
+
limit: int,
|
|
599
|
+
) -> list[CommentWithMetadata]:
|
|
600
|
+
query = """
|
|
601
|
+
select
|
|
602
|
+
c.id,
|
|
603
|
+
c.author_id,
|
|
604
|
+
c.content,
|
|
605
|
+
c.subject_type,
|
|
606
|
+
c.subject_id,
|
|
607
|
+
c.parent_comment_id,
|
|
608
|
+
c.status,
|
|
609
|
+
c.edited_count,
|
|
610
|
+
c.edited_at,
|
|
611
|
+
c.created_at
|
|
612
|
+
from "comment".comments c
|
|
613
|
+
where true
|
|
614
|
+
and c.created_at <= $1
|
|
615
|
+
order by c.created_at desc
|
|
616
|
+
limit $2
|
|
617
|
+
"""
|
|
618
|
+
async with self.read_database.get_connection() as connection:
|
|
619
|
+
result = await connection.fetch(query, cursor, limit)
|
|
620
|
+
return [self._map_comment_with_metadata_row(row) for row in result]
|
|
621
|
+
|
|
622
|
+
async def select_comment_with_metadata_by_id(self, *, comment_id: str) -> CommentWithMetadata:
|
|
623
|
+
query = """
|
|
624
|
+
select
|
|
625
|
+
c.id,
|
|
626
|
+
c.author_id,
|
|
627
|
+
c.content,
|
|
628
|
+
c.subject_type,
|
|
629
|
+
c.subject_id,
|
|
630
|
+
c.parent_comment_id,
|
|
631
|
+
c.status,
|
|
632
|
+
c.edited_count,
|
|
633
|
+
c.edited_at,
|
|
634
|
+
c.created_at
|
|
635
|
+
from "comment".comments c
|
|
636
|
+
where c.id = $1
|
|
637
|
+
"""
|
|
638
|
+
async with self.read_database.get_connection() as connection:
|
|
639
|
+
row = await connection.fetchrow(query, comment_id)
|
|
640
|
+
if row is None:
|
|
641
|
+
raise CommentNotFoundException()
|
|
642
|
+
return self._map_comment_with_metadata_row(row)
|
|
643
|
+
|
|
644
|
+
async def select_comment_preview_mapping(self, *, comment_ids: set[str]) -> dict[str, CommentPreview]:
|
|
645
|
+
if not comment_ids:
|
|
646
|
+
return {}
|
|
647
|
+
query = """
|
|
648
|
+
select
|
|
649
|
+
id,
|
|
650
|
+
author_id,
|
|
651
|
+
content,
|
|
652
|
+
status
|
|
653
|
+
from "comment".comments
|
|
654
|
+
where id = any($1::varchar[])
|
|
655
|
+
"""
|
|
656
|
+
async with self.read_database.get_connection() as connection:
|
|
657
|
+
result = await connection.fetch(query, list(comment_ids))
|
|
658
|
+
return {row["id"]: self._map_comment_preview_row(row) for row in result}
|
|
659
|
+
|
|
660
|
+
async def select_comments_by_subject(
|
|
661
|
+
self,
|
|
662
|
+
*,
|
|
663
|
+
subject_type: CommentSubjectType,
|
|
664
|
+
subject_id: str,
|
|
665
|
+
cursor: datetime,
|
|
666
|
+
limit: int,
|
|
667
|
+
viewer_id: str | None,
|
|
668
|
+
) -> list[Comment]:
|
|
669
|
+
query = """
|
|
670
|
+
with viewer_context as (
|
|
671
|
+
select exists(
|
|
672
|
+
select 1
|
|
673
|
+
from "comment".user_restrictions_lookup
|
|
674
|
+
where true
|
|
675
|
+
and user_id = $3
|
|
676
|
+
and restriction_type = 'muted'
|
|
677
|
+
) as is_viewer_muted
|
|
678
|
+
)
|
|
679
|
+
select
|
|
680
|
+
c.id,
|
|
681
|
+
c.author_id,
|
|
682
|
+
c.content,
|
|
683
|
+
c.edited_count,
|
|
684
|
+
c.edited_at,
|
|
685
|
+
c.level,
|
|
686
|
+
c.created_at
|
|
687
|
+
from "comment".comments c
|
|
688
|
+
left join "comment".user_restrictions_lookup r
|
|
689
|
+
on r.user_id = c.author_id
|
|
690
|
+
cross join viewer_context vc
|
|
691
|
+
where true
|
|
692
|
+
and c.subject_type = $1::"comment".comment_subject_type
|
|
693
|
+
and c.subject_id = $2
|
|
694
|
+
and c.status = 'active'::"comment".comment_status
|
|
695
|
+
and c.created_at <= $4
|
|
696
|
+
and (
|
|
697
|
+
r.user_id is null
|
|
698
|
+
or (
|
|
699
|
+
r.restriction_type = 'muted'
|
|
700
|
+
and $3 is not null
|
|
701
|
+
and vc.is_viewer_muted
|
|
702
|
+
and c.author_id = $3
|
|
703
|
+
)
|
|
704
|
+
)
|
|
705
|
+
and (
|
|
706
|
+
$3 is null
|
|
707
|
+
or not exists(
|
|
708
|
+
select 1
|
|
709
|
+
from "comment".user_blocks_lookup b
|
|
710
|
+
where true
|
|
711
|
+
and b.blocker_id = $3
|
|
712
|
+
and b.blocked_id = c.author_id
|
|
713
|
+
)
|
|
714
|
+
)
|
|
715
|
+
order by c.created_at desc
|
|
716
|
+
limit $5
|
|
717
|
+
"""
|
|
718
|
+
async with self.read_database.get_connection() as connection:
|
|
719
|
+
result = await connection.fetch(
|
|
720
|
+
query,
|
|
721
|
+
subject_type.value,
|
|
722
|
+
subject_id,
|
|
723
|
+
viewer_id,
|
|
724
|
+
cursor,
|
|
725
|
+
limit,
|
|
726
|
+
)
|
|
727
|
+
return [self._map_comment_row(row) for row in result]
|
|
728
|
+
|
|
729
|
+
async def select_comments_by_author_id(
|
|
730
|
+
self,
|
|
731
|
+
*,
|
|
732
|
+
author_id: str,
|
|
733
|
+
cursor: datetime,
|
|
734
|
+
limit: int,
|
|
735
|
+
viewer_id: str | None,
|
|
736
|
+
) -> list[Comment]:
|
|
737
|
+
query = """
|
|
738
|
+
with viewer_context as (
|
|
739
|
+
select exists(
|
|
740
|
+
select 1
|
|
741
|
+
from "comment".user_restrictions_lookup
|
|
742
|
+
where true
|
|
743
|
+
and user_id = $2
|
|
744
|
+
and restriction_type = 'muted'
|
|
745
|
+
) as is_viewer_muted
|
|
746
|
+
)
|
|
747
|
+
select
|
|
748
|
+
c.id,
|
|
749
|
+
c.author_id,
|
|
750
|
+
c.content,
|
|
751
|
+
c.edited_count,
|
|
752
|
+
c.edited_at,
|
|
753
|
+
c.level,
|
|
754
|
+
c.created_at
|
|
755
|
+
from "comment".comments c
|
|
756
|
+
left join "comment".user_restrictions_lookup r
|
|
757
|
+
on r.user_id = c.author_id
|
|
758
|
+
cross join viewer_context vc
|
|
759
|
+
where true
|
|
760
|
+
and c.author_id = $1
|
|
761
|
+
and c.status = 'active'::"comment".comment_status
|
|
762
|
+
and c.created_at <= $3
|
|
763
|
+
and (
|
|
764
|
+
r.user_id is null
|
|
765
|
+
or (
|
|
766
|
+
r.restriction_type = 'muted'
|
|
767
|
+
and $2 is not null
|
|
768
|
+
and vc.is_viewer_muted
|
|
769
|
+
and c.author_id = $2
|
|
770
|
+
)
|
|
771
|
+
)
|
|
772
|
+
and (
|
|
773
|
+
$2 is null
|
|
774
|
+
or not exists(
|
|
775
|
+
select 1
|
|
776
|
+
from "comment".user_blocks_lookup b
|
|
777
|
+
where true
|
|
778
|
+
and b.blocker_id = $2
|
|
779
|
+
and b.blocked_id = c.author_id
|
|
780
|
+
)
|
|
781
|
+
)
|
|
782
|
+
order by c.created_at desc
|
|
783
|
+
limit $4
|
|
784
|
+
"""
|
|
785
|
+
async with self.read_database.get_connection() as connection:
|
|
786
|
+
result = await connection.fetch(
|
|
787
|
+
query,
|
|
788
|
+
author_id,
|
|
789
|
+
viewer_id,
|
|
790
|
+
cursor,
|
|
791
|
+
limit,
|
|
792
|
+
)
|
|
793
|
+
return [self._map_comment_row(row) for row in result]
|
|
794
|
+
|
|
795
|
+
async def select_replies_by_parent_id(
|
|
796
|
+
self,
|
|
797
|
+
*,
|
|
798
|
+
parent_comment_id: str,
|
|
799
|
+
cursor: datetime,
|
|
800
|
+
limit: int,
|
|
801
|
+
viewer_id: str | None,
|
|
802
|
+
) -> list[Comment]:
|
|
803
|
+
query = """
|
|
804
|
+
with viewer_context as (
|
|
805
|
+
select exists(
|
|
806
|
+
select 1
|
|
807
|
+
from "comment".user_restrictions_lookup
|
|
808
|
+
where true
|
|
809
|
+
and user_id = $2
|
|
810
|
+
and restriction_type = 'muted'
|
|
811
|
+
) as is_viewer_muted
|
|
812
|
+
)
|
|
813
|
+
select
|
|
814
|
+
c.id,
|
|
815
|
+
c.author_id,
|
|
816
|
+
c.content,
|
|
817
|
+
c.edited_count,
|
|
818
|
+
c.edited_at,
|
|
819
|
+
c.level,
|
|
820
|
+
c.created_at
|
|
821
|
+
from "comment".comments c
|
|
822
|
+
left join "comment".user_restrictions_lookup r
|
|
823
|
+
on r.user_id = c.author_id
|
|
824
|
+
cross join viewer_context vc
|
|
825
|
+
where true
|
|
826
|
+
and c.parent_comment_id = $1
|
|
827
|
+
and c.status = 'active'::"comment".comment_status
|
|
828
|
+
and c.created_at <= $3
|
|
829
|
+
and (
|
|
830
|
+
r.user_id is null
|
|
831
|
+
or (
|
|
832
|
+
r.restriction_type = 'muted'
|
|
833
|
+
and $2 is not null
|
|
834
|
+
and vc.is_viewer_muted
|
|
835
|
+
and c.author_id = $2
|
|
836
|
+
)
|
|
837
|
+
)
|
|
838
|
+
and (
|
|
839
|
+
$2 is null
|
|
840
|
+
or not exists(
|
|
841
|
+
select 1
|
|
842
|
+
from "comment".user_blocks_lookup b
|
|
843
|
+
where true
|
|
844
|
+
and b.blocker_id = $2
|
|
845
|
+
and b.blocked_id = c.author_id
|
|
846
|
+
)
|
|
847
|
+
)
|
|
848
|
+
order by c.created_at desc
|
|
849
|
+
limit $4
|
|
850
|
+
"""
|
|
851
|
+
async with self.read_database.get_connection() as connection:
|
|
852
|
+
result = await connection.fetch(
|
|
853
|
+
query,
|
|
854
|
+
parent_comment_id,
|
|
855
|
+
viewer_id,
|
|
856
|
+
cursor,
|
|
857
|
+
limit,
|
|
858
|
+
)
|
|
859
|
+
return [self._map_comment_row(row) for row in result]
|
|
860
|
+
|
|
861
|
+
async def select_comment_by_id(
|
|
862
|
+
self,
|
|
863
|
+
*,
|
|
864
|
+
comment_id: str,
|
|
865
|
+
viewer_id: str | None,
|
|
866
|
+
) -> Comment | None:
|
|
867
|
+
query = """
|
|
868
|
+
with viewer_context as (
|
|
869
|
+
select exists(
|
|
870
|
+
select 1
|
|
871
|
+
from "comment".user_restrictions_lookup
|
|
872
|
+
where true
|
|
873
|
+
and user_id = $2
|
|
874
|
+
and restriction_type = 'muted'
|
|
875
|
+
) as is_viewer_muted
|
|
876
|
+
)
|
|
877
|
+
select
|
|
878
|
+
c.id,
|
|
879
|
+
c.author_id,
|
|
880
|
+
c.content,
|
|
881
|
+
c.edited_count,
|
|
882
|
+
c.edited_at,
|
|
883
|
+
c.level,
|
|
884
|
+
c.created_at
|
|
885
|
+
from "comment".comments c
|
|
886
|
+
left join "comment".user_restrictions_lookup r
|
|
887
|
+
on r.user_id = c.author_id
|
|
888
|
+
cross join viewer_context vc
|
|
889
|
+
where true
|
|
890
|
+
and c.id = $1
|
|
891
|
+
and c.status = 'active'::"comment".comment_status
|
|
892
|
+
and (
|
|
893
|
+
r.user_id is null
|
|
894
|
+
or (
|
|
895
|
+
r.restriction_type = 'muted'
|
|
896
|
+
and $2 is not null
|
|
897
|
+
and vc.is_viewer_muted
|
|
898
|
+
and c.author_id = $2
|
|
899
|
+
)
|
|
900
|
+
)
|
|
901
|
+
and (
|
|
902
|
+
$2 is null
|
|
903
|
+
or not exists(
|
|
904
|
+
select 1
|
|
905
|
+
from "comment".user_blocks_lookup b
|
|
906
|
+
where true
|
|
907
|
+
and b.blocker_id = $2
|
|
908
|
+
and b.blocked_id = c.author_id
|
|
909
|
+
)
|
|
910
|
+
)
|
|
911
|
+
"""
|
|
912
|
+
async with self.read_database.get_connection() as connection:
|
|
913
|
+
row = await connection.fetchrow(query, comment_id, viewer_id)
|
|
914
|
+
return self._map_comment_row(row) if row is not None else None
|
|
915
|
+
|
|
916
|
+
def _map_comment_row(self, row: Record) -> Comment:
|
|
917
|
+
return Comment(
|
|
918
|
+
id=row["id"],
|
|
919
|
+
author_id=row["author_id"],
|
|
920
|
+
content=row["content"],
|
|
921
|
+
edited_count=row["edited_count"],
|
|
922
|
+
edited_at=row["edited_at"],
|
|
923
|
+
level=row["level"],
|
|
924
|
+
created_at=row["created_at"],
|
|
925
|
+
)
|
|
926
|
+
|
|
927
|
+
def _map_comment_with_metadata_row(self, row: Record) -> CommentWithMetadata:
|
|
928
|
+
return CommentWithMetadata(
|
|
929
|
+
id=row["id"],
|
|
930
|
+
author_id=row["author_id"],
|
|
931
|
+
content=row["content"],
|
|
932
|
+
subject_type=CommentSubjectType(row["subject_type"]),
|
|
933
|
+
subject_id=row["subject_id"],
|
|
934
|
+
parent_comment_id=row["parent_comment_id"],
|
|
935
|
+
status=CommentStatus(row["status"]),
|
|
936
|
+
edited_count=row["edited_count"],
|
|
937
|
+
edited_at=row["edited_at"],
|
|
938
|
+
created_at=row["created_at"],
|
|
939
|
+
)
|
|
940
|
+
|
|
941
|
+
def _map_comment_preview_row(self, row: Record) -> CommentPreview:
|
|
942
|
+
return CommentPreview(
|
|
943
|
+
id=row["id"],
|
|
944
|
+
author_id=row["author_id"],
|
|
945
|
+
content=row["content"] or "",
|
|
946
|
+
status=CommentStatus(row["status"]),
|
|
947
|
+
)
|