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.
Files changed (222) hide show
  1. core_framework/__init__.py +0 -0
  2. core_framework/alembic/comment/alembic/README +1 -0
  3. core_framework/alembic/comment/alembic/env.py +72 -0
  4. core_framework/alembic/comment/alembic/script.py.mako +28 -0
  5. core_framework/alembic/comment/alembic/versions/30334fd1347b_init.py +59 -0
  6. core_framework/alembic/comment/alembic/versions/a2b3c4d5e6f7_improve_comment_indexes.py +54 -0
  7. core_framework/alembic/comment/alembic/versions/bcc8e00cfc8b_add_extra_tables.py +64 -0
  8. core_framework/alembic/comment/alembic/versions/d1e2f3a4b5c6_add_comment_stats_dirty_table.py +29 -0
  9. core_framework/alembic/comment/alembic/versions/e3f4a5b6c7d8_cascade_delete_comment_descendants.py +49 -0
  10. core_framework/alembic/comment/alembic/versions/f7e6d5c4b3a2_comments_path_to_ltree.py +60 -0
  11. core_framework/alembic/comment/alembic.ini +52 -0
  12. core_framework/alembic/extension/alembic/README +1 -0
  13. core_framework/alembic/extension/alembic/env.py +98 -0
  14. core_framework/alembic/extension/alembic/script.py.mako +28 -0
  15. core_framework/alembic/extension/alembic/versions/0389226049cb_add_pg_trgm_extension.py +25 -0
  16. core_framework/alembic/extension/alembic/versions/5dc58b016cf5_add_citext_extension.py +25 -0
  17. core_framework/alembic/extension/alembic/versions/b0ba0d8a284e_add_pg_stat_statements_extension.py +25 -0
  18. core_framework/alembic/extension/alembic/versions/c9d0e1f2a3b4_add_ltree_extension.py +25 -0
  19. core_framework/alembic/extension/alembic.ini +147 -0
  20. core_framework/alembic/moderation/alembic/README +1 -0
  21. core_framework/alembic/moderation/alembic/env.py +98 -0
  22. core_framework/alembic/moderation/alembic/script.py.mako +28 -0
  23. core_framework/alembic/moderation/alembic/versions/085ba9021850_add_category_to_user_restrictions.py +93 -0
  24. core_framework/alembic/moderation/alembic/versions/5f9e4fc14a41_create_moderation_appeals_table.py +69 -0
  25. core_framework/alembic/moderation/alembic/versions/63e37381e73b_add_user_reports_table.py +33 -0
  26. core_framework/alembic/moderation/alembic/versions/6a2ae31b7ac6_add_moderation_actions_table.py +34 -0
  27. core_framework/alembic/moderation/alembic/versions/716aa1735c03_improve_indexes.py +36 -0
  28. core_framework/alembic/moderation/alembic/versions/7d243ddbfde1_add_post_reports_table.py +35 -0
  29. core_framework/alembic/moderation/alembic/versions/8fba1f72dd46_add_indexes.py +64 -0
  30. core_framework/alembic/moderation/alembic/versions/95cc35a51984_update_restriction_history.py +91 -0
  31. core_framework/alembic/moderation/alembic/versions/9ad79d0af730_add_unique_constraint_user_reports_.py +28 -0
  32. core_framework/alembic/moderation/alembic/versions/a5e569f5df1a_create_user_restrictions_table.py +38 -0
  33. core_framework/alembic/moderation/alembic/versions/b2c3d4e5f6a7_add_indexes.py +42 -0
  34. core_framework/alembic/moderation/alembic/versions/c3d4e5f6a7b8_improve_report_indexes.py +48 -0
  35. core_framework/alembic/moderation/alembic/versions/d4af74643ff5_add_internal_notes_table.py +38 -0
  36. core_framework/alembic/moderation/alembic/versions/db20f2fb7390_add_comment_reports_table.py +35 -0
  37. core_framework/alembic/moderation/alembic/versions/e66226952ea6_add_report_category_to_user_reports_.py +54 -0
  38. core_framework/alembic/moderation/alembic/versions/f5e8cb275c30_enforce_1_pending_appeal.py +29 -0
  39. core_framework/alembic/moderation/alembic/versions/fe1faad2832d_create_restriction_history_table.py +69 -0
  40. core_framework/alembic/moderation/alembic.ini +147 -0
  41. core_framework/alembic/post/alembic/README +1 -0
  42. core_framework/alembic/post/alembic/env.py +97 -0
  43. core_framework/alembic/post/alembic/script.py.mako +28 -0
  44. core_framework/alembic/post/alembic/versions/51542673f5c8_add_tables_for_muted_banned_users.py +41 -0
  45. core_framework/alembic/post/alembic/versions/5beeeae40a4a_add_post_views_table.py +45 -0
  46. core_framework/alembic/post/alembic/versions/620971509a8b_init.py +55 -0
  47. core_framework/alembic/post/alembic/versions/a1b2c3d4e5f6_add_indexes.py +44 -0
  48. core_framework/alembic/post/alembic/versions/c1d2e3f4a5b6_add_post_hashtags_table.py +36 -0
  49. core_framework/alembic/post/alembic/versions/e56723f2afff_add_post_stats_table.py +39 -0
  50. core_framework/alembic/post/alembic/versions/fbc723ac58cc_add_post_likes_table.py +32 -0
  51. core_framework/alembic/post/alembic.ini +149 -0
  52. core_framework/alembic/user/alembic/README +1 -0
  53. core_framework/alembic/user/alembic/env.py +98 -0
  54. core_framework/alembic/user/alembic/script.py.mako +28 -0
  55. core_framework/alembic/user/alembic/versions/1a8bb99726ed_remove_avatar_id_from_users.py +81 -0
  56. core_framework/alembic/user/alembic/versions/2ccacf455941_improve_indexes.py +34 -0
  57. core_framework/alembic/user/alembic/versions/47f47ce2110e_create_user_deletions_table.py +31 -0
  58. core_framework/alembic/user/alembic/versions/5976db3f0175_drop_user_states.py +26 -0
  59. core_framework/alembic/user/alembic/versions/62417002cf32_add_indexes.py +46 -0
  60. core_framework/alembic/user/alembic/versions/6f7ccf3c226b_refactor_user_login_events.py +66 -0
  61. core_framework/alembic/user/alembic/versions/73432817015b_add_user_preferences_table.py +33 -0
  62. core_framework/alembic/user/alembic/versions/765bc01a7a59_create_user_blocks_table.py +33 -0
  63. core_framework/alembic/user/alembic/versions/7a56631f9927_create_user_login_events_table.py +49 -0
  64. core_framework/alembic/user/alembic/versions/831611e589bc_create_user_state.py +31 -0
  65. core_framework/alembic/user/alembic/versions/83c98ab2a779_add_user_profiles_table.py +88 -0
  66. core_framework/alembic/user/alembic/versions/8a94362cad6d_create_user_role.py +31 -0
  67. core_framework/alembic/user/alembic/versions/94b973923895_add_user_change_history_table.py +97 -0
  68. core_framework/alembic/user/alembic/versions/cbc0f4efe84f_add_avatar_id_column_to_users_table.py +31 -0
  69. core_framework/alembic/user/alembic/versions/d8b98ac6b073_add_index_for_get_admin_user_ids_query.py +29 -0
  70. core_framework/alembic/user/alembic/versions/ddb70cc09d16_create_user_states_table.py +34 -0
  71. core_framework/alembic/user/alembic/versions/f9ba10815ecd_add_users_table.py +33 -0
  72. core_framework/alembic/user/alembic.ini +147 -0
  73. core_framework/api/__init__.py +0 -0
  74. core_framework/api/admin/__init__.py +0 -0
  75. core_framework/api/admin/comments/router.py +69 -0
  76. core_framework/api/admin/comments/schemas.py +53 -0
  77. core_framework/api/admin/moderation/__init__.py +0 -0
  78. core_framework/api/admin/moderation/router.py +205 -0
  79. core_framework/api/admin/moderation/schemas.py +110 -0
  80. core_framework/api/admin/posts/router.py +62 -0
  81. core_framework/api/admin/posts/schemas.py +29 -0
  82. core_framework/api/admin/router.py +17 -0
  83. core_framework/api/admin/users/__init__.py +0 -0
  84. core_framework/api/admin/users/router.py +181 -0
  85. core_framework/api/admin/users/schemas.py +137 -0
  86. core_framework/api/auth/__init__.py +0 -0
  87. core_framework/api/auth/router.py +21 -0
  88. core_framework/api/auth/schemas.py +28 -0
  89. core_framework/api/comments/authenticated/router.py +126 -0
  90. core_framework/api/comments/authenticated/schemas.py +27 -0
  91. core_framework/api/comments/public/router.py +103 -0
  92. core_framework/api/comments/public/schemas.py +36 -0
  93. core_framework/api/comments/router.py +9 -0
  94. core_framework/api/comments/schemas.py +17 -0
  95. core_framework/api/dependencies.py +168 -0
  96. core_framework/api/events/router.py +39 -0
  97. core_framework/api/events/schemas.py +20 -0
  98. core_framework/api/posts/authenticated/router.py +83 -0
  99. core_framework/api/posts/authenticated/schemas.py +37 -0
  100. core_framework/api/posts/public/router.py +100 -0
  101. core_framework/api/posts/public/schemas.py +39 -0
  102. core_framework/api/posts/router.py +9 -0
  103. core_framework/api/posts/schemas.py +39 -0
  104. core_framework/api/router.py +19 -0
  105. core_framework/api/schemas.py +9 -0
  106. core_framework/api/system/__init__.py +0 -0
  107. core_framework/api/system/router.py +108 -0
  108. core_framework/api/users/__init__.py +0 -0
  109. core_framework/api/users/authenticated/__init__.py +0 -0
  110. core_framework/api/users/authenticated/router.py +244 -0
  111. core_framework/api/users/authenticated/schemas.py +81 -0
  112. core_framework/api/users/public/__init__.py +0 -0
  113. core_framework/api/users/public/router.py +25 -0
  114. core_framework/api/users/public/schemas.py +7 -0
  115. core_framework/api/users/router.py +9 -0
  116. core_framework/api/users/shared/schemas.py +174 -0
  117. core_framework/application/__init__.py +0 -0
  118. core_framework/application/auth/__init__.py +0 -0
  119. core_framework/application/auth/access_service.py +26 -0
  120. core_framework/application/auth/auth_service.py +10 -0
  121. core_framework/application/auth/models.py +10 -0
  122. core_framework/application/bootstrap.py +19 -0
  123. core_framework/application/comments/admin_service.py +236 -0
  124. core_framework/application/comments/aggregation_service.py +28 -0
  125. core_framework/application/comments/authenticated_service.py +89 -0
  126. core_framework/application/comments/public_service.py +218 -0
  127. core_framework/application/events/README.md +26 -0
  128. core_framework/application/events/event_service.py +51 -0
  129. core_framework/application/events/event_token.py +46 -0
  130. core_framework/application/events/models.py +9 -0
  131. core_framework/application/moderation/__init__.py +0 -0
  132. core_framework/application/moderation/appeal_service.py +98 -0
  133. core_framework/application/moderation/moderator_service.py +46 -0
  134. core_framework/application/moderation/report_service.py +180 -0
  135. core_framework/application/moderation/scheduled_service.py +5 -0
  136. core_framework/application/moderation/user_service.py +180 -0
  137. core_framework/application/posts/admin_service.py +104 -0
  138. core_framework/application/posts/aggregation_service.py +28 -0
  139. core_framework/application/posts/authenticated_service.py +72 -0
  140. core_framework/application/posts/public_service.py +197 -0
  141. core_framework/application/shared/__init__.py +0 -0
  142. core_framework/application/shared/enums.py +16 -0
  143. core_framework/application/shared/exceptions.py +16 -0
  144. core_framework/application/shared/user_agent.py +24 -0
  145. core_framework/application/users/__init__.py +0 -0
  146. core_framework/application/users/admin_service.py +298 -0
  147. core_framework/application/users/authenticated_service.py +179 -0
  148. core_framework/application/users/public_service.py +7 -0
  149. core_framework/application/users/scheduled_service.py +5 -0
  150. core_framework/bundled_alembic.py +57 -0
  151. core_framework/core/__init__.py +37 -0
  152. core_framework/core/cache.py +234 -0
  153. core_framework/core/context.py +14 -0
  154. core_framework/core/database.py +111 -0
  155. core_framework/core/exception_handlers/__init__.py +3 -0
  156. core_framework/core/exception_handlers/comment.py +99 -0
  157. core_framework/core/exception_handlers/common.py +5 -0
  158. core_framework/core/exception_handlers/moderation.py +104 -0
  159. core_framework/core/exception_handlers/post.py +54 -0
  160. core_framework/core/exception_handlers/setup.py +80 -0
  161. core_framework/core/exception_handlers/user.py +72 -0
  162. core_framework/core/http_client.py +64 -0
  163. core_framework/core/logging.py +99 -0
  164. core_framework/core/middleware.py +64 -0
  165. core_framework/core/observability.py +36 -0
  166. core_framework/core/pagination.py +203 -0
  167. core_framework/core/redis.py +135 -0
  168. core_framework/core/runtime.py +66 -0
  169. core_framework/core/settings.py +189 -0
  170. core_framework/domains/__init__.py +0 -0
  171. core_framework/domains/comment/README.md +243 -0
  172. core_framework/domains/comment/__init__.py +25 -0
  173. core_framework/domains/comment/constants.py +3 -0
  174. core_framework/domains/comment/dependencies.py +29 -0
  175. core_framework/domains/comment/enums.py +11 -0
  176. core_framework/domains/comment/exceptions.py +31 -0
  177. core_framework/domains/comment/models.py +54 -0
  178. core_framework/domains/comment/repository.py +947 -0
  179. core_framework/domains/comment/service.py +259 -0
  180. core_framework/domains/moderation/README.md +138 -0
  181. core_framework/domains/moderation/__init__.py +47 -0
  182. core_framework/domains/moderation/dependencies.py +29 -0
  183. core_framework/domains/moderation/enums.py +62 -0
  184. core_framework/domains/moderation/exceptions.py +31 -0
  185. core_framework/domains/moderation/models.py +94 -0
  186. core_framework/domains/moderation/repository.py +828 -0
  187. core_framework/domains/moderation/service.py +334 -0
  188. core_framework/domains/post/README.md +182 -0
  189. core_framework/domains/post/__init__.py +22 -0
  190. core_framework/domains/post/constants.py +3 -0
  191. core_framework/domains/post/dependencies.py +29 -0
  192. core_framework/domains/post/enums.py +18 -0
  193. core_framework/domains/post/exceptions.py +21 -0
  194. core_framework/domains/post/models.py +53 -0
  195. core_framework/domains/post/repository.py +791 -0
  196. core_framework/domains/post/service.py +204 -0
  197. core_framework/domains/user/README.md +74 -0
  198. core_framework/domains/user/__init__.py +39 -0
  199. core_framework/domains/user/constants.py +8 -0
  200. core_framework/domains/user/dependencies.py +29 -0
  201. core_framework/domains/user/enums.py +19 -0
  202. core_framework/domains/user/exceptions.py +31 -0
  203. core_framework/domains/user/models.py +124 -0
  204. core_framework/domains/user/repository.py +612 -0
  205. core_framework/domains/user/service.py +257 -0
  206. core_framework/domains/user/utils.py +182 -0
  207. core_framework/main.py +104 -0
  208. core_framework/worker/__init__.py +0 -0
  209. core_framework/worker/main.py +56 -0
  210. core_framework/worker/schedules/__init__.py +35 -0
  211. core_framework/worker/schedules/schedule_aggregate_comment_stats.py +32 -0
  212. core_framework/worker/schedules/schedule_aggregate_post_view_counts.py +28 -0
  213. core_framework/worker/schedules/schedule_expired_account_deletions.py +24 -0
  214. core_framework/worker/schedules/schedule_expired_mute_lifts.py +24 -0
  215. core_framework/worker/tasks/__init__.py +11 -0
  216. core_framework/worker/tasks/process_account_deletion.py +13 -0
  217. core_framework/worker/tasks/process_aggregate_comment_stats.py +19 -0
  218. core_framework/worker/tasks/process_aggregate_post_stats.py +12 -0
  219. core_framework/worker/tasks/process_mute_lift.py +13 -0
  220. core_framework-0.3.0.dist-info/METADATA +22 -0
  221. core_framework-0.3.0.dist-info/RECORD +222 -0
  222. core_framework-0.3.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,28 @@
1
+ """${message}
2
+
3
+ Revision ID: ${up_revision}
4
+ Revises: ${down_revision | comma,n}
5
+ Create Date: ${create_date}
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+ ${imports if imports else ""}
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = ${repr(up_revision)}
16
+ down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)}
17
+ branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
18
+ depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
19
+
20
+
21
+ def upgrade() -> None:
22
+ """Upgrade schema."""
23
+ ${upgrades if upgrades else "pass"}
24
+
25
+
26
+ def downgrade() -> None:
27
+ """Downgrade schema."""
28
+ ${downgrades if downgrades else "pass"}
@@ -0,0 +1,81 @@
1
+ """remove avatar_id from users
2
+
3
+ Revision ID: 1a8bb99726ed
4
+ Revises: 83c98ab2a779
5
+ Create Date: 2026-02-10 08:09:13.094952
6
+
7
+ """
8
+
9
+ from alembic import op
10
+
11
+ # revision identifiers, used by Alembic.
12
+ revision: str = "1a8bb99726ed"
13
+ down_revision: str | list[str] | None = "83c98ab2a779"
14
+ branch_labels: str | list[str] | None = None
15
+ depends_on: str | list[str] | None = None
16
+
17
+
18
+ def upgrade() -> None:
19
+ op.execute("""
20
+ create or replace function log_user_update_change()
21
+ returns trigger as $$
22
+ begin
23
+ if tg_op = 'UPDATE' then
24
+ if new.username is distinct from old.username then
25
+ insert into "user".user_change_history (user_id, actor_id, entity_type, field, old_value, new_value)
26
+ values (new.user_id, current_setting('app.actor_id', true), 'users', 'username', old.username, new.username);
27
+ end if;
28
+ if new.role is distinct from old.role then
29
+ insert into "user".user_change_history (user_id, actor_id, entity_type, field, old_value, new_value)
30
+ values (new.user_id, current_setting('app.actor_id', true), 'users', 'role', old.role::text, new.role::text);
31
+ end if;
32
+ end if;
33
+ return new;
34
+ end;
35
+ $$ language plpgsql;
36
+ """)
37
+ op.execute("drop index if exists idx_users_created_at")
38
+ op.execute("""
39
+ create index idx_users_created_at
40
+ on users (created_at desc)
41
+ include (user_id, username, role)
42
+ """)
43
+ op.execute("""
44
+ alter table users
45
+ drop column avatar_id
46
+ """)
47
+
48
+
49
+ def downgrade() -> None:
50
+ op.execute("""
51
+ alter table users
52
+ add column avatar_id varchar(36) null
53
+ """)
54
+ op.execute("""
55
+ create or replace function log_user_update_change()
56
+ returns trigger as $$
57
+ begin
58
+ if tg_op = 'UPDATE' then
59
+ if new.username is distinct from old.username then
60
+ insert into "user".user_change_history (user_id, actor_id, entity_type, field, old_value, new_value)
61
+ values (new.user_id, current_setting('app.actor_id', true), 'users', 'username', old.username, new.username);
62
+ end if;
63
+ if new.role is distinct from old.role then
64
+ insert into "user".user_change_history (user_id, actor_id, entity_type, field, old_value, new_value)
65
+ values (new.user_id, current_setting('app.actor_id', true), 'users', 'role', old.role::text, new.role::text);
66
+ end if;
67
+ if new.avatar_id is distinct from old.avatar_id then
68
+ insert into "user".user_change_history (user_id, actor_id, entity_type, field, old_value, new_value)
69
+ values (new.user_id, current_setting('app.actor_id', true), 'users', 'avatar_id', old.avatar_id, new.avatar_id);
70
+ end if;
71
+ end if;
72
+ return new;
73
+ end;
74
+ $$ language plpgsql;
75
+ """)
76
+ op.execute("drop index if exists idx_users_created_at")
77
+ op.execute("""
78
+ create index idx_users_created_at
79
+ on users (created_at desc)
80
+ include (user_id, username, role, avatar_id)
81
+ """)
@@ -0,0 +1,34 @@
1
+ """improve indexes
2
+
3
+ Revision ID: 2ccacf455941
4
+ Revises: 94b973923895
5
+ Create Date: 2026-02-08 12:04:49.377562
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ from alembic import op
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision: str = "2ccacf455941"
15
+ down_revision: str | Sequence[str] | None = "94b973923895"
16
+ branch_labels: str | Sequence[str] | None = None
17
+
18
+
19
+ def upgrade() -> None:
20
+ op.execute("""
21
+ create index idx_user_deletions_scheduled_for
22
+ on user_deletions (scheduled_for)
23
+ """)
24
+
25
+ op.execute('set search_path to "user", extension')
26
+ op.execute("""
27
+ create index idx_users_username_trgm
28
+ on users using gin (username gin_trgm_ops)
29
+ """)
30
+
31
+
32
+ def downgrade() -> None:
33
+ op.execute("drop index if exists idx_user_deletions_scheduled_for")
34
+ op.execute("drop index if exists idx_users_username_trgm")
@@ -0,0 +1,31 @@
1
+ """create user_deletions table
2
+
3
+ Revision ID: 47f47ce2110e
4
+ Revises: d8b98ac6b073
5
+ Create Date: 2026-01-15 06:32:50.121454
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ from alembic import op
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision: str = "47f47ce2110e"
15
+ down_revision: str | Sequence[str] | None = "d8b98ac6b073"
16
+ branch_labels: str | Sequence[str] | None = None
17
+ depends_on: str | Sequence[str] | None = None
18
+
19
+
20
+ def upgrade() -> None:
21
+ op.execute("""
22
+ create table user_deletions (
23
+ user_id varchar(128) primary key references users(user_id) on delete cascade,
24
+ scheduled_for timestamptz not null,
25
+ created_at timestamptz not null default now()
26
+ );
27
+ """)
28
+
29
+
30
+ def downgrade() -> None:
31
+ op.execute("drop table if exists user_deletions;")
@@ -0,0 +1,26 @@
1
+ """drop user_states
2
+
3
+ Revision ID: 5976db3f0175
4
+ Revises: cbc0f4efe84f
5
+ Create Date: 2026-01-21 06:32:32.524025
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ from alembic import op
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision: str = "5976db3f0175"
15
+ down_revision: str | Sequence[str] | None = "cbc0f4efe84f"
16
+ branch_labels: str | Sequence[str] | None = None
17
+ depends_on: str | Sequence[str] | None = None
18
+
19
+
20
+ def upgrade() -> None:
21
+ op.execute("drop table if exists user_states;")
22
+ op.execute("drop type if exists user_state;")
23
+
24
+
25
+ def downgrade() -> None:
26
+ pass
@@ -0,0 +1,46 @@
1
+ """add indexes
2
+
3
+ Revision ID: 62417002cf32
4
+ Revises: 5976db3f0175
5
+ Create Date: 2026-02-04 12:34:27.662946
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ from alembic import op
12
+
13
+ revision: str = "62417002cf32"
14
+ down_revision: str | Sequence[str] | None = "5976db3f0175"
15
+ branch_labels: str | Sequence[str] | None = None
16
+ depends_on: str | Sequence[str] | None = None
17
+
18
+
19
+ def upgrade() -> None:
20
+ op.execute("""
21
+ create index idx_users_created_at
22
+ on users (created_at desc)
23
+ include (user_id, username, role, avatar_id)
24
+ """)
25
+
26
+ op.execute("""
27
+ create index idx_user_blocks_blocker_created
28
+ on user_blocks (blocker_id, created_at desc)
29
+ """)
30
+
31
+ op.execute("""
32
+ create index idx_user_login_events_user_id
33
+ on user_login_events (user_id)
34
+ """)
35
+
36
+ op.execute("""
37
+ create index idx_user_blocks_blocked_id
38
+ on user_blocks (blocked_id)
39
+ """)
40
+
41
+
42
+ def downgrade() -> None:
43
+ op.execute("drop index if exists idx_users_created_at")
44
+ op.execute("drop index if exists idx_user_blocks_blocker_created")
45
+ op.execute("drop index if exists idx_user_login_events_user_id")
46
+ op.execute("drop index if exists idx_user_blocks_blocked_id")
@@ -0,0 +1,66 @@
1
+ """refactor user_login_events
2
+
3
+ Revision ID: 6f7ccf3c226b
4
+ Revises: 73432817015b
5
+ Create Date: 2026-03-01 08:18:47.625133
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ from alembic import op
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision: str = "6f7ccf3c226b"
15
+ down_revision: str | Sequence[str] | None = "73432817015b"
16
+ branch_labels: str | Sequence[str] | None = None
17
+ depends_on: str | Sequence[str] | None = None
18
+
19
+
20
+ def upgrade() -> None:
21
+ op.execute("drop table if exists user_login_events;")
22
+ op.execute("drop table if exists user_devices;")
23
+ op.execute("""
24
+ create table user_login_events (
25
+ id bigint generated always as identity primary key,
26
+ user_id varchar(128) not null references users(user_id) on delete cascade,
27
+ created_at timestamptz not null default now(),
28
+ request_context jsonb not null default '{}'::jsonb
29
+ );
30
+ """)
31
+ op.execute("""
32
+ create index idx_user_login_events_user_id_created_at
33
+ on user_login_events (user_id, created_at);
34
+ """)
35
+
36
+
37
+ def downgrade() -> None:
38
+ op.execute("drop index if exists idx_user_login_events_user_id_created_at;")
39
+ op.execute("drop table if exists user_login_events;")
40
+ op.execute("""
41
+ create table user_devices (
42
+ id bigint generated always as identity primary key,
43
+ device_key text not null unique,
44
+ device_type text not null,
45
+ first_seen_at timestamptz not null default now(),
46
+ last_seen_at timestamptz not null default now(),
47
+ login_count bigint not null default 0
48
+ );
49
+ """)
50
+ op.execute("""
51
+ create table user_login_events (
52
+ id bigint generated always as identity primary key,
53
+ user_id varchar(128) not null references users(user_id) on delete cascade,
54
+ created_at timestamptz not null default now(),
55
+ user_device_id bigint null references user_devices(id) on delete set null,
56
+ provider text not null,
57
+ user_agent text,
58
+ ip_address inet,
59
+ country_code varchar(2) not null,
60
+ constraint country_code_format check (country_code ~ '^[A-Z]{2}$')
61
+ );
62
+ """)
63
+ op.execute("""
64
+ create index idx_user_login_events_user_id
65
+ on user_login_events (user_id);
66
+ """)
@@ -0,0 +1,33 @@
1
+ """add user_preferences table
2
+
3
+ Revision ID: 73432817015b
4
+ Revises: 1a8bb99726ed
5
+ Create Date: 2026-02-16 06:08:24.726281
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ from alembic import op
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision: str = "73432817015b"
15
+ down_revision: str | Sequence[str] | None = "1a8bb99726ed"
16
+ branch_labels: str | Sequence[str] | None = None
17
+ depends_on: str | Sequence[str] | None = None
18
+
19
+
20
+ def upgrade() -> None:
21
+ op.execute("""
22
+ create table user_preferences (
23
+ user_id varchar(128) primary key references users(user_id) on delete cascade,
24
+ theme varchar(20) not null default 'system',
25
+ language varchar(10) not null default 'en',
26
+ created_at timestamptz not null default now(),
27
+ updated_at timestamptz not null default now()
28
+ )
29
+ """)
30
+
31
+
32
+ def downgrade() -> None:
33
+ op.execute("drop table if exists user_preferences;")
@@ -0,0 +1,33 @@
1
+ """create user_blocks table
2
+
3
+ Revision ID: 765bc01a7a59
4
+ Revises: ddb70cc09d16
5
+ Create Date: 2025-12-28 07:11:48.877991
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ from alembic import op
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision: str = "765bc01a7a59"
15
+ down_revision: str | Sequence[str] | None = "ddb70cc09d16"
16
+ branch_labels: str | Sequence[str] | None = None
17
+ depends_on: str | Sequence[str] | None = None
18
+
19
+
20
+ def upgrade() -> None:
21
+ op.execute("""
22
+ create table user_blocks (
23
+ blocker_id varchar(128) not null references users(user_id) on delete cascade,
24
+ blocked_id varchar(128) not null references users(user_id) on delete cascade,
25
+ created_at timestamptz not null default now(),
26
+ primary key (blocker_id, blocked_id),
27
+ constraint no_self_block check (blocker_id != blocked_id)
28
+ );
29
+ """)
30
+
31
+
32
+ def downgrade() -> None:
33
+ op.execute("drop table if exists user_blocks")
@@ -0,0 +1,49 @@
1
+ """create user_login_events table
2
+
3
+ Revision ID: 7a56631f9927
4
+ Revises: 765bc01a7a59
5
+ Create Date: 2025-12-28 07:23:14.355428
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ from alembic import op
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision: str = "7a56631f9927"
15
+ down_revision: str | Sequence[str] | None = "765bc01a7a59"
16
+ branch_labels: str | Sequence[str] | None = None
17
+ depends_on: str | Sequence[str] | None = None
18
+
19
+
20
+ def upgrade() -> None:
21
+ op.execute("""
22
+ create table user_devices (
23
+ id bigint generated always as identity primary key,
24
+ device_key text not null unique,
25
+ device_type text not null,
26
+ first_seen_at timestamptz not null default now(),
27
+ last_seen_at timestamptz not null default now(),
28
+ login_count bigint not null default 0
29
+ );
30
+ """)
31
+
32
+ op.execute("""
33
+ create table user_login_events (
34
+ id bigint generated always as identity primary key,
35
+ user_id varchar(128) not null references users(user_id) on delete cascade,
36
+ created_at timestamptz not null default now(),
37
+ user_device_id bigint null references user_devices(id) on delete set null,
38
+ provider text not null,
39
+ user_agent text,
40
+ ip_address inet,
41
+ country_code varchar(2) not null,
42
+ constraint country_code_format check (country_code ~ '^[A-Z]{2}$')
43
+ );
44
+ """)
45
+
46
+
47
+ def downgrade() -> None:
48
+ op.execute("drop table if exists user_login_events")
49
+ op.execute("drop table if exists user_devices")
@@ -0,0 +1,31 @@
1
+ """create user_state
2
+
3
+ Revision ID: 831611e589bc
4
+ Revises: f9ba10815ecd
5
+ Create Date: 2025-12-27 09:30:08.446039
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ from alembic import op
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision: str = "831611e589bc"
15
+ down_revision: str | Sequence[str] | None = "f9ba10815ecd"
16
+ branch_labels: str | Sequence[str] | None = None
17
+ depends_on: str | Sequence[str] | None = None
18
+
19
+
20
+ def upgrade() -> None:
21
+ op.execute("""
22
+ create type user_state as enum (
23
+ 'active',
24
+ 'muted',
25
+ 'banned'
26
+ );
27
+ """)
28
+
29
+
30
+ def downgrade() -> None:
31
+ op.execute("drop type if exists user_state")
@@ -0,0 +1,88 @@
1
+ """add user_profiles table
2
+
3
+ Revision ID: 83c98ab2a779
4
+ Revises: 2ccacf455941
5
+ Create Date: 2026-02-09 06:15:09.093893
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ from alembic import op
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision: str = "83c98ab2a779"
15
+ down_revision: str | Sequence[str] | None = "2ccacf455941"
16
+ branch_labels: str | Sequence[str] | None = None
17
+ depends_on: str | Sequence[str] | None = None
18
+
19
+
20
+ def upgrade() -> None:
21
+ op.execute("""
22
+ create type profile_visibility as enum ('public', 'private', 'members_only')
23
+ """)
24
+ op.execute("""
25
+ create table user_profiles (
26
+ user_id varchar(128) primary key references users(user_id) on delete cascade,
27
+ avatar_id text,
28
+ banner_id text,
29
+ display_name varchar(100),
30
+ bio varchar(500),
31
+ status varchar(150),
32
+ social_links jsonb not null default '{}',
33
+ profile_visibility profile_visibility not null default 'public',
34
+ created_at timestamptz not null default now(),
35
+ updated_at timestamptz not null default now()
36
+ )
37
+ """)
38
+ op.execute("""
39
+ create or replace function log_profile_changes()
40
+ returns trigger as $$
41
+ begin
42
+ if tg_op = 'UPDATE' then
43
+ if new.avatar_id is distinct from old.avatar_id then
44
+ insert into "user".user_change_history (user_id, actor_id, entity_type, field, old_value, new_value)
45
+ values (new.user_id, current_setting('app.actor_id', true), 'user_profiles', 'avatar_id', old.avatar_id, new.avatar_id);
46
+ end if;
47
+ if new.banner_id is distinct from old.banner_id then
48
+ insert into "user".user_change_history (user_id, actor_id, entity_type, field, old_value, new_value)
49
+ values (new.user_id, current_setting('app.actor_id', true), 'user_profiles', 'banner_id', old.banner_id, new.banner_id);
50
+ end if;
51
+ if new.display_name is distinct from old.display_name then
52
+ insert into "user".user_change_history (user_id, actor_id, entity_type, field, old_value, new_value)
53
+ values (new.user_id, current_setting('app.actor_id', true), 'user_profiles', 'display_name', old.display_name, new.display_name);
54
+ end if;
55
+ if new.bio is distinct from old.bio then
56
+ insert into "user".user_change_history (user_id, actor_id, entity_type, field, old_value, new_value)
57
+ values (new.user_id, current_setting('app.actor_id', true), 'user_profiles', 'bio', old.bio, new.bio);
58
+ end if;
59
+ if new.status is distinct from old.status then
60
+ insert into "user".user_change_history (user_id, actor_id, entity_type, field, old_value, new_value)
61
+ values (new.user_id, current_setting('app.actor_id', true), 'user_profiles', 'status', old.status, new.status);
62
+ end if;
63
+ if new.social_links is distinct from old.social_links then
64
+ insert into "user".user_change_history (user_id, actor_id, entity_type, field, old_value, new_value)
65
+ values (new.user_id, current_setting('app.actor_id', true), 'user_profiles', 'social_links', old.social_links::text, new.social_links::text);
66
+ end if;
67
+ if new.profile_visibility is distinct from old.profile_visibility then
68
+ insert into "user".user_change_history (user_id, actor_id, entity_type, field, old_value, new_value)
69
+ values (new.user_id, current_setting('app.actor_id', true), 'user_profiles', 'profile_visibility', old.profile_visibility, new.profile_visibility);
70
+ end if;
71
+ end if;
72
+ return new;
73
+ end;
74
+ $$ language plpgsql;
75
+ """)
76
+ op.execute("""
77
+ create trigger profile_change_history_trigger
78
+ after update on user_profiles
79
+ for each row
80
+ execute function log_profile_changes();
81
+ """)
82
+
83
+
84
+ def downgrade() -> None:
85
+ op.execute("drop trigger if exists profile_change_history_trigger on user_profiles;")
86
+ op.execute("drop function if exists log_profile_changes();")
87
+ op.execute("drop table if exists user_profiles;")
88
+ op.execute("drop type if exists profile_visibility;")
@@ -0,0 +1,31 @@
1
+ """init users table
2
+
3
+ Revision ID: 8a94362cad6d
4
+ Revises:
5
+ Create Date: 2025-12-26 07:49:20.727570
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ from alembic import op
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision: str = "8a94362cad6d"
15
+ down_revision: str | Sequence[str] | None = None
16
+ branch_labels: str | Sequence[str] | None = None
17
+ depends_on: str | Sequence[str] | None = None
18
+
19
+
20
+ def upgrade() -> None:
21
+ op.execute("""
22
+ create type user_role as enum (
23
+ 'admin',
24
+ 'moderator',
25
+ 'member'
26
+ );
27
+ """)
28
+
29
+
30
+ def downgrade() -> None:
31
+ op.execute("drop type if exists user_role")