core-framework 1.7.0__tar.gz → 1.8.0__tar.gz

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 (484) hide show
  1. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/rules/application-layer.mdc +4 -0
  2. core_framework-1.8.0/.cursor/rules/docstrings.mdc +81 -0
  3. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/rules/domain-services-and-adapters.mdc +4 -0
  4. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/skills/code-review/SKILL.md +2 -1
  5. {core_framework-1.7.0 → core_framework-1.8.0}/CHANGELOG.md +20 -1
  6. {core_framework-1.7.0 → core_framework-1.8.0}/PKG-INFO +2 -1
  7. core_framework-1.8.0/alembic/comment/alembic/versions/comment_add_comment_mentions.py +36 -0
  8. core_framework-1.8.0/alembic/post/alembic/versions/post_add_post_mentions.py +36 -0
  9. {core_framework-1.7.0 → core_framework-1.8.0}/config.toml +2 -0
  10. {core_framework-1.7.0 → core_framework-1.8.0}/config.toml.template +2 -0
  11. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/admin/users/schemas.py +2 -0
  12. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/comments/authenticated/mappers.py +11 -1
  13. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/comments/authenticated/schemas.py +8 -0
  14. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/comments/public/schemas.py +1 -0
  15. core_framework-1.8.0/core_framework/api/posts/authenticated/mappers.py +28 -0
  16. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/posts/authenticated/schemas.py +8 -0
  17. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/posts/public/schemas.py +1 -0
  18. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/users/authenticated/router.py +11 -5
  19. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/users/authenticated/schemas.py +2 -0
  20. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/users/public/router.py +13 -3
  21. core_framework-1.8.0/core_framework/api/users/public/schemas.py +33 -0
  22. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/auth/access_service.py +2 -7
  23. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/auth/auth_service.py +1 -8
  24. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/bootstrap.py +1 -0
  25. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/cache/invalidate_service.py +4 -20
  26. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/comments/admin_service.py +8 -30
  27. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/comments/authenticated_service.py +16 -38
  28. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/comments/public_service.py +65 -56
  29. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/events/event_service.py +4 -13
  30. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/events/event_token.py +3 -0
  31. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/media/avatar_service.py +10 -37
  32. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/media/banner_service.py +1 -0
  33. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/moderation/appeal_service.py +3 -24
  34. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/moderation/moderator_service.py +2 -9
  35. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/moderation/report_service.py +2 -11
  36. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/moderation/scheduled_service.py +2 -4
  37. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/moderation/user_service.py +12 -70
  38. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/notifications/inbox_service.py +4 -27
  39. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/notifications/mute_service.py +1 -9
  40. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/notifications/notification_service.py +2 -12
  41. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/posts/admin_service.py +8 -22
  42. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/posts/authenticated_service.py +8 -21
  43. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/posts/public_service.py +83 -26
  44. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/shared/user_agent.py +1 -0
  45. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/shared/worker_jobs.py +2 -0
  46. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/users/admin_service.py +30 -88
  47. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/users/authenticated_service.py +38 -40
  48. core_framework-1.8.0/core_framework/application/users/public_service.py +155 -0
  49. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/core/cache.py +8 -0
  50. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/core/settings.py +14 -2
  51. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/comment/README.md +18 -1
  52. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/comment/constants.py +2 -0
  53. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/comment/dependencies.py +27 -0
  54. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/comment/models.py +32 -2
  55. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/comment/repository.py +461 -7
  56. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/comment/service.py +185 -25
  57. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/__init__.py +4 -0
  58. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/avatar/components/staging.py +9 -0
  59. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/avatar/components/variant_encoder.py +15 -6
  60. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/avatar/constants.py +2 -0
  61. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/avatar/exceptions.py +5 -1
  62. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/avatar/ports/variant_encoder.py +2 -2
  63. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/banner/components/finals_publisher.py +13 -2
  64. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/banner/components/staging.py +40 -4
  65. core_framework-1.8.0/core_framework/domains/media/banner/components/variant_encoder.py +32 -0
  66. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/banner/constants.py +2 -0
  67. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/banner/exceptions.py +5 -1
  68. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/banner/ports/variant_encoder.py +2 -2
  69. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/banner/service.py +141 -0
  70. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/dependencies.py +101 -1
  71. core_framework-1.8.0/core_framework/domains/media/shared/constants.py +4 -0
  72. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/shared/exceptions.py +4 -0
  73. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/shared/repository.py +176 -0
  74. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/shared/utils.py +22 -0
  75. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/moderation/dependencies.py +27 -0
  76. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/moderation/repository.py +469 -0
  77. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/moderation/service.py +253 -29
  78. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/notification/README.md +1 -1
  79. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/notification/dependencies.py +27 -0
  80. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/notification/repository.py +163 -0
  81. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/notification/service.py +55 -7
  82. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/post/README.md +11 -7
  83. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/post/constants.py +1 -0
  84. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/post/dependencies.py +27 -0
  85. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/post/models.py +37 -1
  86. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/post/repository.py +443 -11
  87. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/post/service.py +146 -16
  88. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/user/dependencies.py +27 -0
  89. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/user/models.py +69 -0
  90. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/user/repository.py +519 -0
  91. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/user/service.py +325 -34
  92. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/user/utils.py +15 -0
  93. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/utils.py +81 -0
  94. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/infrastructure/media/local_banner_staging_adapter.py +28 -0
  95. core_framework-1.8.0/core_framework/infrastructure/media/pillow_avatar_variant_encoder_adapter.py +50 -0
  96. core_framework-1.8.0/core_framework/infrastructure/media/pillow_banner_variant_encoder_adapter.py +33 -0
  97. core_framework-1.8.0/core_framework/infrastructure/media/pillow_staging_image.py +72 -0
  98. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/infrastructure/media/ssh_banner_finals_publisher_adapter.py +19 -0
  99. {core_framework-1.7.0 → core_framework-1.8.0}/docs/deployments/README.md +1 -0
  100. {core_framework-1.7.0 → core_framework-1.8.0}/docs/deployments/deploy-playbook.md +6 -0
  101. core_framework-1.8.0/docs/deployments/release-playbook.md +84 -0
  102. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/README.md +12 -12
  103. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/cache/admin_cache_invalidation.md +8 -6
  104. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/comments/admin_comments.md +2 -1
  105. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/comments/create_comment.md +20 -5
  106. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/comments/edit_comment.md +12 -8
  107. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/comments/retrieve_comments.md +5 -5
  108. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/media/avatar-upload-design.md +9 -8
  109. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/media/banner-upload-design.md +1 -1
  110. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/media/upload-pipeline-design.md +9 -9
  111. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/mentions/mentions_in_content.md +74 -14
  112. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/posts/admin_posts.md +1 -0
  113. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/posts/author_context.md +3 -1
  114. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/posts/hashtag_discovery.md +1 -1
  115. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/users/avatar.md +7 -7
  116. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/users/banner.md +3 -3
  117. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/users/follow.md +1 -1
  118. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/users/profile.md +12 -5
  119. core_framework-1.8.0/docs/domains/users/public_profile.md +97 -0
  120. {core_framework-1.7.0 → core_framework-1.8.0}/docs/library/api.md +1 -0
  121. {core_framework-1.7.0 → core_framework-1.8.0}/docs/platform/domain-services-and-adapters.md +10 -8
  122. {core_framework-1.7.0 → core_framework-1.8.0}/docs/platform/patch-update-typed-payload.md +3 -1
  123. {core_framework-1.7.0 → core_framework-1.8.0}/docs/todo.md +1 -0
  124. {core_framework-1.7.0 → core_framework-1.8.0}/pyproject.toml +2 -1
  125. core_framework-1.8.0/tests/integration/api/mentions/mentions_integration_test.py +348 -0
  126. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/users/authenticated/avatar_upload_test.py +35 -0
  127. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/users/authenticated/banner_upload_test.py +35 -0
  128. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/users/authenticated/router_test.py +28 -7
  129. core_framework-1.8.0/tests/integration/api/users/public/router_test.py +331 -0
  130. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/worker/avatar_upload_test.py +67 -2
  131. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/worker/banner_upload_test.py +67 -2
  132. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/worker/utils_test.py +14 -1
  133. {core_framework-1.7.0 → core_framework-1.8.0}/uv.lock +28 -1
  134. core_framework-1.7.0/core_framework/api/posts/authenticated/mappers.py +0 -14
  135. core_framework-1.7.0/core_framework/api/users/public/schemas.py +0 -7
  136. core_framework-1.7.0/core_framework/application/users/public_service.py +0 -9
  137. core_framework-1.7.0/core_framework/domains/media/banner/components/variant_encoder.py +0 -16
  138. core_framework-1.7.0/core_framework/infrastructure/media/pillow_banner_variant_encoder_adapter.py +0 -36
  139. core_framework-1.7.0/core_framework/infrastructure/media/pillow_variant_encoder_adapter.py +0 -61
  140. core_framework-1.7.0/tests/integration/api/users/public/router_test.py +0 -49
  141. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/rules/api-layer.mdc +0 -0
  142. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/rules/api-reference-docs.mdc +0 -0
  143. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/rules/api-security.mdc +0 -0
  144. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/rules/api-validation.mdc +0 -0
  145. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/rules/code-review-output.mdc +0 -0
  146. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/rules/constants-final.mdc +0 -0
  147. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/rules/database-triggers.md +0 -0
  148. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/rules/domain-caller-context.mdc +0 -0
  149. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/rules/domain-imports.mdc +0 -0
  150. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/rules/domain-input-guards.mdc +0 -0
  151. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/rules/domain-repository-exceptions.mdc +0 -0
  152. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/rules/exception-handlers.mdc +0 -0
  153. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/rules/flow-documentation.mdc +0 -0
  154. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/rules/henry-cursor-rules-sync.mdc +0 -0
  155. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/rules/implementation-workflow.mdc +0 -0
  156. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/rules/integration-test-strategy.mdc +0 -0
  157. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/rules/karpathy-guidelines.mdc +0 -0
  158. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/rules/layer-boundaries.mdc +0 -0
  159. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/rules/no-code-in-docs.mdc +0 -0
  160. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/rules/postgres-config-conventions.mdc +0 -0
  161. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/rules/repository-read-consistency.mdc +0 -0
  162. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/rules/strong-read-opt-in.mdc +0 -0
  163. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/rules/structured-logging.mdc +0 -0
  164. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/rules/tech-stack.mdc +0 -0
  165. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/skills/add-config/SKILL.md +0 -0
  166. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/skills/add-domain/SKILL.md +0 -0
  167. {core_framework-1.7.0 → core_framework-1.8.0}/.cursor/skills/recommend-features/SKILL.md +0 -0
  168. {core_framework-1.7.0 → core_framework-1.8.0}/.dockerignore +0 -0
  169. {core_framework-1.7.0 → core_framework-1.8.0}/.github/workflows/_deploy.yml +0 -0
  170. {core_framework-1.7.0 → core_framework-1.8.0}/.github/workflows/dev-ci-cd.yaml +0 -0
  171. {core_framework-1.7.0 → core_framework-1.8.0}/.github/workflows/manual-deployment.yaml +0 -0
  172. {core_framework-1.7.0 → core_framework-1.8.0}/.github/workflows/publish-pypi.yml +0 -0
  173. {core_framework-1.7.0 → core_framework-1.8.0}/.github/workflows/test.yaml +0 -0
  174. {core_framework-1.7.0 → core_framework-1.8.0}/.gitignore +0 -0
  175. {core_framework-1.7.0 → core_framework-1.8.0}/.pre-commit-config.yaml +0 -0
  176. {core_framework-1.7.0 → core_framework-1.8.0}/.python-version +0 -0
  177. {core_framework-1.7.0 → core_framework-1.8.0}/LICENSE +0 -0
  178. {core_framework-1.7.0 → core_framework-1.8.0}/README.md +0 -0
  179. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/comment/alembic/README +0 -0
  180. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/comment/alembic/env.py +0 -0
  181. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/comment/alembic/script.py.mako +0 -0
  182. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/comment/alembic/versions/v1_comment_init_baseline.py +0 -0
  183. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/comment/alembic.ini +0 -0
  184. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/extension/alembic/README +0 -0
  185. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/extension/alembic/env.py +0 -0
  186. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/extension/alembic/script.py.mako +0 -0
  187. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/extension/alembic/versions/v1_ext_init_baseline.py +0 -0
  188. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/extension/alembic.ini +0 -0
  189. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/media/alembic/README +0 -0
  190. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/media/alembic/env.py +0 -0
  191. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/media/alembic/script.py.mako +0 -0
  192. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/media/alembic/versions/media_add_banner_staging_and_lease.py +0 -0
  193. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/media/alembic/versions/v1_media_init_baseline.py +0 -0
  194. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/media/alembic.ini +0 -0
  195. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/moderation/alembic/README +0 -0
  196. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/moderation/alembic/env.py +0 -0
  197. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/moderation/alembic/script.py.mako +0 -0
  198. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/moderation/alembic/versions/v1_mod_init_baseline.py +0 -0
  199. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/moderation/alembic.ini +0 -0
  200. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/notification/alembic/README +0 -0
  201. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/notification/alembic/env.py +0 -0
  202. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/notification/alembic/script.py.mako +0 -0
  203. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/notification/alembic/versions/v1_notif_init_baseline.py +0 -0
  204. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/notification/alembic.ini +0 -0
  205. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/post/alembic/README +0 -0
  206. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/post/alembic/env.py +0 -0
  207. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/post/alembic/script.py.mako +0 -0
  208. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/post/alembic/versions/v1_post_init_baseline.py +0 -0
  209. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/post/alembic.ini +0 -0
  210. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/user/alembic/README +0 -0
  211. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/user/alembic/env.py +0 -0
  212. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/user/alembic/script.py.mako +0 -0
  213. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/user/alembic/versions/user_add_avatar_ingest_sequences.py +0 -0
  214. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/user/alembic/versions/user_add_banner_ingest_sequences.py +0 -0
  215. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/user/alembic/versions/user_add_date_of_birth_to_profiles.py +0 -0
  216. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/user/alembic/versions/v1_user_init_baseline.py +0 -0
  217. {core_framework-1.7.0 → core_framework-1.8.0}/alembic/user/alembic.ini +0 -0
  218. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/__init__.py +0 -0
  219. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/__init__.py +0 -0
  220. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/admin/__init__.py +0 -0
  221. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/admin/cache/router.py +0 -0
  222. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/admin/cache/schemas.py +0 -0
  223. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/admin/comments/router.py +0 -0
  224. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/admin/comments/schemas.py +0 -0
  225. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/admin/moderation/__init__.py +0 -0
  226. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/admin/moderation/router.py +0 -0
  227. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/admin/moderation/schemas.py +0 -0
  228. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/admin/posts/router.py +0 -0
  229. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/admin/posts/schemas.py +0 -0
  230. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/admin/router.py +0 -0
  231. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/admin/users/__init__.py +0 -0
  232. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/admin/users/router.py +0 -0
  233. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/auth/__init__.py +0 -0
  234. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/auth/router.py +0 -0
  235. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/auth/schemas.py +0 -0
  236. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/comments/authenticated/router.py +0 -0
  237. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/comments/public/router.py +0 -0
  238. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/comments/router.py +0 -0
  239. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/comments/schemas.py +0 -0
  240. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/constants.py +0 -0
  241. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/dependencies.py +0 -0
  242. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/events/router.py +0 -0
  243. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/events/schemas.py +0 -0
  244. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/notifications/authenticated/router.py +0 -0
  245. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/notifications/authenticated/schemas.py +0 -0
  246. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/notifications/router.py +0 -0
  247. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/posts/authenticated/router.py +0 -0
  248. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/posts/public/router.py +0 -0
  249. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/posts/router.py +0 -0
  250. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/posts/schemas.py +0 -0
  251. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/router.py +0 -0
  252. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/schemas.py +0 -0
  253. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/system/__init__.py +0 -0
  254. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/system/router.py +0 -0
  255. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/users/__init__.py +0 -0
  256. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/users/authenticated/__init__.py +0 -0
  257. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/users/authenticated/mappers.py +0 -0
  258. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/users/mappers.py +0 -0
  259. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/users/public/__init__.py +0 -0
  260. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/users/router.py +1 -1
  261. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/api/users/shared/schemas.py +0 -0
  262. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/__init__.py +0 -0
  263. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/auth/__init__.py +0 -0
  264. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/auth/models.py +0 -0
  265. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/cache/models.py +0 -0
  266. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/comments/aggregation_service.py +0 -0
  267. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/events/README.md +0 -0
  268. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/events/models.py +0 -0
  269. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/media/README.md +0 -0
  270. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/moderation/__init__.py +0 -0
  271. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/notifications/README.md +0 -0
  272. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/notifications/enums.py +0 -0
  273. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/posts/aggregation_service.py +0 -0
  274. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/shared/__init__.py +0 -0
  275. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/shared/enums.py +0 -0
  276. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/shared/exceptions.py +0 -0
  277. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/users/__init__.py +0 -0
  278. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/users/aggregation_service.py +0 -0
  279. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/application/users/scheduled_service.py +0 -0
  280. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/asgi.py +0 -0
  281. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/bundled_alembic.py +0 -0
  282. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/constants.py +0 -0
  283. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/core/__init__.py +0 -0
  284. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/core/context.py +0 -0
  285. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/core/database.py +0 -0
  286. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/core/exception_handlers/__init__.py +0 -0
  287. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/core/exception_handlers/comment.py +0 -0
  288. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/core/exception_handlers/common.py +0 -0
  289. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/core/exception_handlers/media.py +0 -0
  290. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/core/exception_handlers/moderation.py +0 -0
  291. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/core/exception_handlers/notification.py +0 -0
  292. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/core/exception_handlers/post.py +0 -0
  293. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/core/exception_handlers/setup.py +0 -0
  294. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/core/exception_handlers/user.py +0 -0
  295. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/core/firebase.py +0 -0
  296. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/core/http_client.py +0 -0
  297. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/core/logging.py +0 -0
  298. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/core/middleware.py +0 -0
  299. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/core/observability.py +0 -0
  300. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/core/pagination.py +0 -0
  301. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/core/redis.py +0 -0
  302. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/core/runtime.py +0 -0
  303. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/__init__.py +0 -0
  304. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/comment/__init__.py +0 -0
  305. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/comment/enums.py +0 -0
  306. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/comment/exceptions.py +0 -0
  307. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/exceptions.py +0 -0
  308. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/README.md +0 -0
  309. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/avatar/__init__.py +0 -0
  310. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/avatar/components/__init__.py +0 -0
  311. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/avatar/components/finals_publisher.py +0 -0
  312. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/avatar/models.py +0 -0
  313. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/avatar/ports/__init__.py +0 -0
  314. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/avatar/ports/finals_publisher.py +0 -0
  315. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/avatar/ports/staging.py +0 -0
  316. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/avatar/service.py +0 -0
  317. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/banner/models.py +0 -0
  318. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/banner/ports/finals_publisher.py +0 -0
  319. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/banner/ports/staging.py +0 -0
  320. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/shared/__init__.py +0 -0
  321. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/media/shared/service.py +0 -0
  322. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/moderation/README.md +0 -0
  323. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/moderation/__init__.py +0 -0
  324. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/moderation/enums.py +0 -0
  325. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/moderation/exceptions.py +0 -0
  326. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/moderation/models.py +0 -0
  327. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/notification/__init__.py +0 -0
  328. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/notification/enums.py +0 -0
  329. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/notification/exceptions.py +0 -0
  330. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/notification/models.py +0 -0
  331. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/post/__init__.py +0 -0
  332. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/post/enums.py +0 -0
  333. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/post/exceptions.py +0 -0
  334. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/user/README.md +0 -0
  335. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/user/__init__.py +0 -0
  336. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/user/constants.py +0 -0
  337. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/user/enums.py +0 -0
  338. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/domains/user/exceptions.py +0 -0
  339. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/infrastructure/__init__.py +0 -0
  340. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/infrastructure/media/__init__.py +0 -0
  341. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/infrastructure/media/local_staging_adapter.py +0 -0
  342. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/infrastructure/media/ssh_finals_publisher_adapter.py +0 -0
  343. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/main.py +0 -0
  344. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/migrate_cli.py +0 -0
  345. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/testing/__init__.py +0 -0
  346. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/testing/arq.py +0 -0
  347. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/testing/auth.py +0 -0
  348. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/testing/config.py +0 -0
  349. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/testing/containers.py +0 -0
  350. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/testing/firebase.py +0 -0
  351. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/testing/hookspecs.py +0 -0
  352. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/testing/httpx_test_client.py +0 -0
  353. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/testing/media.py +0 -0
  354. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/testing/migrations.py +0 -0
  355. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/testing/plugin.py +0 -0
  356. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/worker/__init__.py +0 -0
  357. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/worker/main.py +0 -0
  358. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/worker/schedules/__init__.py +0 -0
  359. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/worker/schedules/schedule_aggregate_comment_stats.py +0 -0
  360. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/worker/schedules/schedule_aggregate_post_stats.py +0 -0
  361. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/worker/schedules/schedule_aggregate_user_stats.py +0 -0
  362. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/worker/schedules/schedule_expired_account_deletions.py +0 -0
  363. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/worker/schedules/schedule_expired_mute_lifts.py +0 -0
  364. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/worker/schedules/schedule_sweep_stale_avatar_staging.py +0 -0
  365. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/worker/schedules/schedule_sweep_stale_banner_staging.py +0 -0
  366. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/worker/tasks/__init__.py +0 -0
  367. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/worker/tasks/delete_superseded_avatar_finals.py +0 -0
  368. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/worker/tasks/delete_superseded_banner_finals.py +0 -0
  369. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/worker/tasks/process_account_deletion.py +0 -0
  370. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/worker/tasks/process_aggregate_comment_stats.py +0 -0
  371. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/worker/tasks/process_aggregate_post_stats.py +0 -0
  372. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/worker/tasks/process_aggregate_user_stats.py +0 -0
  373. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/worker/tasks/process_banner_asset_variants.py +0 -0
  374. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/worker/tasks/process_cache_pattern_invalidation.py +0 -0
  375. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/worker/tasks/process_media_asset_variants.py +0 -0
  376. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/worker/tasks/process_mute_lift.py +0 -0
  377. {core_framework-1.7.0 → core_framework-1.8.0}/core_framework/worker/worker_context.py +0 -0
  378. {core_framework-1.7.0 → core_framework-1.8.0}/docker-compose.dev.yaml +0 -0
  379. {core_framework-1.7.0 → core_framework-1.8.0}/docker-compose.yml +0 -0
  380. {core_framework-1.7.0 → core_framework-1.8.0}/dockerfile +0 -0
  381. {core_framework-1.7.0 → core_framework-1.8.0}/docs/README.md +0 -0
  382. {core_framework-1.7.0 → core_framework-1.8.0}/docs/deployments/edge-upload-limits.md +0 -0
  383. {core_framework-1.7.0 → core_framework-1.8.0}/docs/deployments/examples/Caddyfile.example +0 -0
  384. {core_framework-1.7.0 → core_framework-1.8.0}/docs/deployments/examples/image-server-known-hosts.example +0 -0
  385. {core_framework-1.7.0 → core_framework-1.8.0}/docs/deployments/guides/app-server-deploy-ssh.md +0 -0
  386. {core_framework-1.7.0 → core_framework-1.8.0}/docs/deployments/guides/image-server-ssh.md +0 -0
  387. {core_framework-1.7.0 → core_framework-1.8.0}/docs/deployments/guides/postgres-setup.md +0 -0
  388. {core_framework-1.7.0 → core_framework-1.8.0}/docs/deployments/guides/redis-setup.md +0 -0
  389. {core_framework-1.7.0 → core_framework-1.8.0}/docs/deployments/guides/ubuntu-server-setup.md +0 -0
  390. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/auth/access_control.md +0 -0
  391. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/auth/registration.md +0 -0
  392. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/comments/comment_report.md +0 -0
  393. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/comments/comment_stats_aggregation.md +0 -0
  394. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/comments/delete_comment.md +0 -0
  395. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/events/events.md +0 -0
  396. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/media/upload_pipeline.md +0 -0
  397. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/moderation/appeals.md +0 -0
  398. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/moderation/internal_notes.md +0 -0
  399. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/moderation/moderator_actions.md +0 -0
  400. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/moderation/reports.md +0 -0
  401. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/moderation/restrictions.md +0 -0
  402. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/notifications/notification_inbox.md +0 -0
  403. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/posts/post_like.md +0 -0
  404. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/posts/post_stats_aggregation.md +0 -0
  405. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/posts/post_visibility.md +0 -0
  406. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/users/account.md +0 -0
  407. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/users/account_deletion.md +0 -0
  408. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/users/blocks.md +0 -0
  409. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/users/change_history.md +0 -0
  410. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/users/check_username_exists.md +0 -0
  411. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/users/follow-system-design.md +0 -0
  412. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/users/my_posts_and_comments.md +0 -0
  413. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/users/preferences.md +0 -0
  414. {core_framework-1.7.0 → core_framework-1.8.0}/docs/domains/users/user_removal.md +0 -0
  415. {core_framework-1.7.0 → core_framework-1.8.0}/docs/library/README.md +0 -0
  416. {core_framework-1.7.0 → core_framework-1.8.0}/docs/library/core-framework-migration.md +0 -0
  417. {core_framework-1.7.0 → core_framework-1.8.0}/docs/library/overview.md +0 -0
  418. {core_framework-1.7.0 → core_framework-1.8.0}/docs/library/package-api.md +0 -0
  419. {core_framework-1.7.0 → core_framework-1.8.0}/docs/library/testing-plugin-design.md +0 -0
  420. {core_framework-1.7.0 → core_framework-1.8.0}/docs/platform/README.md +0 -0
  421. {core_framework-1.7.0 → core_framework-1.8.0}/docs/platform/architecture-decisions.md +0 -0
  422. {core_framework-1.7.0 → core_framework-1.8.0}/docs/platform/conventions.md +0 -0
  423. {core_framework-1.7.0 → core_framework-1.8.0}/docs/platform/database-triggers.md +0 -0
  424. {core_framework-1.7.0 → core_framework-1.8.0}/docs/platform/event-outbox-design.md +0 -0
  425. {core_framework-1.7.0 → core_framework-1.8.0}/docs/platform/layers-and-boundaries.md +0 -0
  426. {core_framework-1.7.0 → core_framework-1.8.0}/firebase_config.example.json +0 -0
  427. {core_framework-1.7.0 → core_framework-1.8.0}/makefile +0 -0
  428. {core_framework-1.7.0 → core_framework-1.8.0}/tests/__init__.py +0 -0
  429. {core_framework-1.7.0 → core_framework-1.8.0}/tests/conftest.py +0 -0
  430. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/__init__.py +0 -0
  431. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/__init__.py +0 -0
  432. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/_http_helpers.py +0 -0
  433. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/admin/__init__.py +0 -0
  434. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/admin/cache/__init__.py +0 -0
  435. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/admin/cache/router_test.py +0 -0
  436. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/admin/comments/__init__.py +0 -0
  437. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/admin/comments/router_test.py +0 -0
  438. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/admin/moderation/__init__.py +0 -0
  439. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/admin/moderation/router_test.py +0 -0
  440. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/admin/posts/__init__.py +0 -0
  441. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/admin/posts/router_test.py +0 -0
  442. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/admin/users/__init__.py +0 -0
  443. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/admin/users/router_test.py +0 -0
  444. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/auth/__init__.py +0 -0
  445. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/auth/router_test.py +0 -0
  446. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/comments/__init__.py +0 -0
  447. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/comments/authenticated/comment_writes_integration_test.py +0 -0
  448. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/comments/public/__init__.py +0 -0
  449. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/comments/public/router_test.py +0 -0
  450. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/events/router_test.py +0 -0
  451. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/notifications/__init__.py +0 -0
  452. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/notifications/router_test.py +0 -0
  453. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/posts/__init__.py +0 -0
  454. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/posts/authenticated/post_writes_integration_test.py +0 -0
  455. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/posts/comment_count_aggregation_test.py +0 -0
  456. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/posts/followers_visibility_test.py +0 -0
  457. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/posts/post_stats_dirty_marking_test.py +0 -0
  458. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/posts/public/__init__.py +0 -0
  459. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/posts/public/router_test.py +0 -0
  460. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/system/__init__.py +0 -0
  461. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/system/router_test.py +0 -0
  462. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/users/__init__.py +0 -0
  463. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/users/authenticated/__init__.py +0 -0
  464. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/api/users/public/__init__.py +0 -0
  465. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/worker/__init__.py +0 -0
  466. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/worker/account_deletion_test.py +0 -0
  467. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/worker/aggregate_comment_stats_test.py +0 -0
  468. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/worker/aggregate_post_stats_test.py +0 -0
  469. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/worker/aggregate_user_stats_test.py +0 -0
  470. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/worker/conftest.py +0 -0
  471. {core_framework-1.7.0 → core_framework-1.8.0}/tests/integration/worker/mute_lift_test.py +0 -0
  472. {core_framework-1.7.0 → core_framework-1.8.0}/tests/unit/__init__.py +0 -0
  473. {core_framework-1.7.0 → core_framework-1.8.0}/tests/unit/application/comments/__init__.py +0 -0
  474. {core_framework-1.7.0 → core_framework-1.8.0}/tests/unit/application/events/event_service_test.py +0 -0
  475. {core_framework-1.7.0 → core_framework-1.8.0}/tests/unit/application/notifications/inbox_service_test.py +0 -0
  476. {core_framework-1.7.0 → core_framework-1.8.0}/tests/unit/core/bundled_alembic_test.py +0 -0
  477. {core_framework-1.7.0 → core_framework-1.8.0}/tests/unit/core/migrate_cli_test.py +0 -0
  478. {core_framework-1.7.0 → core_framework-1.8.0}/tests/unit/core/pagination_test.py +0 -0
  479. {core_framework-1.7.0 → core_framework-1.8.0}/tests/unit/domains/__init__.py +0 -0
  480. {core_framework-1.7.0 → core_framework-1.8.0}/tests/unit/domains/comment/__init__.py +0 -0
  481. {core_framework-1.7.0 → core_framework-1.8.0}/tests/unit/domains/user/service_test.py +0 -0
  482. {core_framework-1.7.0 → core_framework-1.8.0}/tests/unit/infrastructure/__init__.py +0 -0
  483. {core_framework-1.7.0 → core_framework-1.8.0}/tests/unit/infrastructure/media/__init__.py +0 -0
  484. {core_framework-1.7.0 → core_framework-1.8.0}/tests/unit/infrastructure/media/ssh_finals_publisher_adapter_test.py +0 -0
@@ -77,3 +77,7 @@ async def retrieve_my_username_suggestions(...) -> list[UsernameSuggestion]: ...
77
77
  - Input validation (`validate_user_id`, `validate_report_id`, etc.) belongs in schemas
78
78
  - Services do NOT raise `RequestValidationError` or import from `fastapi.exceptions`
79
79
  - Routers call `validate_*` from schemas before invoking services
80
+
81
+ ## Docstrings
82
+
83
+ Application functions get a **one-line** docstring (what it does). No `Args` / `Returns` / `Raises` — see **`docstrings.mdc`**.
@@ -0,0 +1,81 @@
1
+ ---
2
+ description: Docstring depth by layer — one-line in application, full Args/Returns/Raises in domain and infrastructure
3
+ globs:
4
+ - core_framework/application/**/*.py
5
+ - core_framework/domains/**/*.py
6
+ - core_framework/infrastructure/**/*.py
7
+ ---
8
+
9
+ # Docstrings by layer
10
+
11
+ Docstring **depth** depends on which layer owns the symbol. Type hints and keyword-only signatures carry parameter detail in application code; domain and infrastructure document the contract at the boundary.
12
+
13
+ ## Application layer (`core_framework/application/`)
14
+
15
+ **One short sentence** — what the function does. No `Args`, `Returns`, or `Raises` sections.
16
+
17
+ ```python
18
+ # ✅ Preferred
19
+ async def enqueue_delete_superseded_banner_finals(*, asset_id: str) -> None:
20
+ """Enqueue cleanup of published finals for a replaced banner asset."""
21
+ ...
22
+
23
+ # ❌ Avoid — too heavy for application orchestration
24
+ async def enqueue_delete_superseded_banner_finals(...):
25
+ """Enqueue cleanup of published finals for a replaced banner asset.
26
+
27
+ Args:
28
+ asset_id: ...
29
+ Returns:
30
+ ...
31
+ """
32
+ ```
33
+
34
+ Applies to **public and private** helpers in application modules. When you touch a function in an application file, use the simple style.
35
+
36
+ ## Domain layer (`core_framework/domains/`)
37
+
38
+ **Full docstring** on public service, repository, component, and port methods that implement or define behavior:
39
+
40
+ - Opening summary (one or two sentences)
41
+ - **`Args:`** — keyword-only parameters and meaning
42
+ - **`Returns:`** — when the return value is not obvious from the type alone
43
+ - **`Raises:`** — domain exceptions and validation errors callers should handle
44
+
45
+ Skip `Raises` only when the method truly cannot raise beyond programming errors. Trivial private helpers may stay undocumented; **exported and orchestration paths** get the full treatment.
46
+
47
+ ```python
48
+ # ✅ Preferred (domain service)
49
+ async def insert_avatar_staging_registry(...) -> AvatarStagingRegistryEntry:
50
+ """Record a new avatar staging registry row before blob upload completes.
51
+
52
+ Args:
53
+ asset_id: Unique avatar asset identifier.
54
+ owner_user_id: User who owns the avatar.
55
+ staging_filename: Basename of the staging blob on disk.
56
+
57
+ Returns:
58
+ Persisted registry entry including staging metadata.
59
+
60
+ Raises:
61
+ DomainValidationError: If required string fields are blank or ``staging_filename``
62
+ is unsafe.
63
+ """
64
+ ```
65
+
66
+ Repositories: document query semantics (filtering, empty results, strong vs normal read) in the summary or **`Returns:`** when non-obvious.
67
+
68
+ ## Infrastructure layer (`core_framework/infrastructure/`)
69
+
70
+ Same as **domain** — adapters implement ports and perform I/O. Document inputs, outputs, failure modes (`Raises:` or raised infrastructure exceptions), and non-obvious side effects (network, disk, SSH).
71
+
72
+ ## When editing
73
+
74
+ - **New** application functions → simple docstring.
75
+ - **New** domain/infrastructure public methods → full docstring.
76
+ - When you **change** a function in a file, bring **that function's** docstring in line with its layer; do not repo-wide migrate unrelated symbols unless the task asks for it.
77
+
78
+ ## Out of scope
79
+
80
+ - **API routers and schemas** — no docstring requirement from this rule (OpenAPI and Pydantic carry the contract).
81
+ - **Workers** (`core_framework/worker/`) — thin task/schedule wrappers follow **application** style; logic belongs in application services.
@@ -45,3 +45,7 @@ See **`docs/platform/domain-services-and-adapters.md`** for the full model. Appl
45
45
  - **Layout:** `domains/media/shared/`, `avatar/ports/`, `avatar/components/`, `AvatarService`.
46
46
  - **Avatar:** `media_deps.avatar_service` only (not individual components).
47
47
  - **Cross-domain:** `avatar_ingest_sequence` / profile `avatar_id` via **user** domain; orchestration in `application/media/avatar_service`.
48
+
49
+ ## Docstrings
50
+
51
+ Domain services, repositories, ports, and components use **full** docstrings (`Args`, `Returns`, `Raises` where applicable). Application orchestration uses **one-line** summaries — see **`docstrings.mdc`**.
@@ -39,7 +39,7 @@ Read these files **before** proceeding with the review:
39
39
  1. **`docs/platform/domain-services-and-adapters.md`** – Application orchestration vs domain services, ports/adapters, multiple collaborators per bounded context
40
40
  1. **`docs/platform/conventions.md`** – Coding conventions, domain rules, caching, Redis, exception handling, API layer rules
41
41
  1. **`docs/platform/architecture-decisions.md`** – Intentional design choices; do not flag these as issues
42
- 1. **`.cursor/rules/api-layer.mdc`**, **`.cursor/rules/application-layer.mdc`**, **`.cursor/rules/domain-services-and-adapters.mdc`**, **`.cursor/rules/domain-imports.mdc`**, **`.cursor/rules/domain-caller-context.mdc`** – Apply when reviewing API/application/domain code
42
+ 1. **`.cursor/rules/api-layer.mdc`**, **`.cursor/rules/application-layer.mdc`**, **`.cursor/rules/docstrings.mdc`**, **`.cursor/rules/domain-services-and-adapters.mdc`**, **`.cursor/rules/domain-imports.mdc`**, **`.cursor/rules/domain-caller-context.mdc`** – Apply when reviewing API/application/domain code
43
43
 
44
44
  ## Step 2: Key Context
45
45
 
@@ -76,6 +76,7 @@ Verify the following. Also apply api-layer, application-layer, and domain-import
76
76
 
77
77
  ### Application Layer Services (`core_framework/application/**/*_service.py`)
78
78
 
79
+ - [ ] **Docstrings** – One-line summary only; no `Args`/`Returns`/`Raises` (see `docstrings.mdc`)
79
80
  - [ ] **No HTTP handling** – No `HTTPException`, `RequestValidationError`, `BackgroundTasks`, or other FastAPI HTTP imports
80
81
  - [ ] **No Pydantic return types** – Services return `dict`, `list[dict]`, dataclasses, or plain types; routers use `response_model`
81
82
  - [ ] **Not found** – Return `None` or `Optional`; routers raise `HTTPException(404)` when appropriate
@@ -4,6 +4,24 @@ Notable changes to **core-framework** (import **`core_framework`**). Format foll
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.8.0] - 2026-06-06
8
+
9
+ ### Added
10
+
11
+ - **`[avatar].public_path_prefix`** and **`[banner].public_path_prefix`** — optional URL path segment in **`config.toml`** when avatar/banner finals are served under a subdirectory on a shared media host. Empty by default (dedicated product vhost docroot).
12
+
13
+ - **Follow counts on profile surfaces:** **`GET /users/me/profile`**, **`PATCH /users/me/profile`**, and admin **`GET /admin/users/{user_id}`** profile payload include **`follower_count`** and **`following_count`** (same **user_stats** source as public profile). Follow/unfollow invalidates **`user_stats`** cache for both parties when the edge changes.
14
+
15
+ - **HEIC/HEIF ingest (avatar and banner):** Allow **`image/heic`** and **`image/heif`** on profile image uploads; decode via **`pillow-heif`** before WebP variant encoding.
16
+
17
+ - **Image ingest hardening:** Staging decode uses a **30s** timeout, **4096×4096** pixel cap, and Pillow decompression-bomb limits; published WebP finals omit embedded metadata.
18
+
19
+ - **Structured mentions (posts and comments):** **`post_mentions`** / **`comment_mentions`** persistence; **`mentions`** on **POST** create, **PATCH** replace (omit unchanged, **`[]`** clears), and **GET** responses as **`UserReference`** list. **`mention` inbox notifications** remain **create-only**. Migrations **`post_add_post_mentions`**, **`comment_add_comment_mentions`**. See **`docs/domains/mentions/mentions_in_content.md`**.
20
+
21
+ ### Database
22
+
23
+ - **Post** migration **`post_add_post_mentions`**; **comment** migration **`comment_add_comment_mentions`**. Run **`uv run cf-alembic`** before deploying.
24
+
7
25
  ## [1.7.0] - 2026-06-01
8
26
 
9
27
  ### Added
@@ -233,4 +251,5 @@ First **SemVer-stable** release per **`docs/library/package-api.md`**.
233
251
  [1.5.0]: https://github.com/NepNepFFXIV/core-framework/compare/v1.4.0...v1.5.0
234
252
  [1.6.0]: https://github.com/NepNepFFXIV/core-framework/compare/v1.5.0...v1.6.0
235
253
  [1.7.0]: https://github.com/NepNepFFXIV/core-framework/compare/v1.6.0...v1.7.0
236
- [unreleased]: https://github.com/NepNepFFXIV/core-framework/compare/v1.7.0...HEAD
254
+ [1.8.0]: https://github.com/NepNepFFXIV/core-framework/compare/v1.7.0...v1.8.0
255
+ [unreleased]: https://github.com/NepNepFFXIV/core-framework/compare/v1.8.0...HEAD
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: core-framework
3
- Version: 1.7.0
3
+ Version: 1.8.0
4
4
  Summary: Core framework package (import as core_framework)
5
5
  Project-URL: Homepage, https://github.com/NepNepFFXIV/core-framework
6
6
  Project-URL: Repository, https://github.com/NepNepFFXIV/core-framework
@@ -24,6 +24,7 @@ Requires-Dist: itsdangerous>=2.2.0
24
24
  Requires-Dist: logfire[asyncpg,fastapi,httpx,redis]>=4.32.1
25
25
  Requires-Dist: mashumaro[orjson]>=3.20
26
26
  Requires-Dist: orjson>=3.11.7
27
+ Requires-Dist: pillow-heif>=1.3.0
27
28
  Requires-Dist: pillow>=12.2.0
28
29
  Requires-Dist: python-ulid>=3.1.0
29
30
  Requires-Dist: structlog>=25.5.0
@@ -0,0 +1,36 @@
1
+ """add comment_mentions for structured @mentions on comments
2
+
3
+ Revision ID: comment_add_comment_mentions
4
+ Revises: v1_comment_init
5
+ Create Date: 2026-06-03 12:00:00.000000
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ from alembic import op
12
+
13
+ revision: str = "comment_add_comment_mentions"
14
+ down_revision: str | Sequence[str] | None = "v1_comment_init"
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 table comment_mentions (
22
+ comment_id varchar(26) not null references comments(id) on delete cascade,
23
+ mentioned_user_id varchar(128) not null,
24
+ created_at timestamptz not null default now(),
25
+ primary key (comment_id, mentioned_user_id)
26
+ );
27
+ """)
28
+
29
+ op.execute("""
30
+ create index idx_comment_mentions_mentioned_user_id
31
+ on comment_mentions (mentioned_user_id);
32
+ """)
33
+
34
+
35
+ def downgrade() -> None:
36
+ pass
@@ -0,0 +1,36 @@
1
+ """add post_mentions for structured @mentions on posts
2
+
3
+ Revision ID: post_add_post_mentions
4
+ Revises: v1_post_init
5
+ Create Date: 2026-06-03 12:00:00.000000
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ from alembic import op
12
+
13
+ revision: str = "post_add_post_mentions"
14
+ down_revision: str | Sequence[str] | None = "v1_post_init"
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 table post_mentions (
22
+ post_id varchar(26) not null references posts(id) on delete cascade,
23
+ mentioned_user_id varchar(128) not null,
24
+ created_at timestamptz not null default now(),
25
+ primary key (post_id, mentioned_user_id)
26
+ );
27
+ """)
28
+
29
+ op.execute("""
30
+ create index idx_post_mentions_mentioned_user_id
31
+ on post_mentions (mentioned_user_id);
32
+ """)
33
+
34
+
35
+ def downgrade() -> None:
36
+ pass
@@ -18,11 +18,13 @@ staging_root = "media-staging"
18
18
  avatar_host_domain = "avatar.core-framework.com"
19
19
  default_avatar_image = "default.webp"
20
20
  publish_root = "/var/www/sites/avatar"
21
+ public_path_prefix = "avatar"
21
22
 
22
23
  [banner]
23
24
  banner_host_domain = "banner.core-framework.com"
24
25
  default_banner_image = "default.webp"
25
26
  publish_root = "/var/www/sites/banner"
27
+ public_path_prefix = "banner"
26
28
 
27
29
  [image_server]
28
30
  host = "127.0.0.1"
@@ -13,11 +13,13 @@ staging_root = "/app/media-staging"
13
13
  avatar_host_domain = "$AVATAR_HOST_DOMAIN"
14
14
  default_avatar_image = "$AVATAR_DEFAULT_IMAGE"
15
15
  publish_root = "$AVATAR_PUBLISH_ROOT"
16
+ public_path_prefix = "avatar"
16
17
 
17
18
  [banner]
18
19
  banner_host_domain = "$BANNER_HOST_DOMAIN"
19
20
  default_banner_image = "$BANNER_DEFAULT_IMAGE"
20
21
  publish_root = "$BANNER_PUBLISH_ROOT"
22
+ public_path_prefix = "banner"
21
23
 
22
24
  [image_server]
23
25
  host = "$IMAGE_SERVER_HOST"
@@ -80,6 +80,8 @@ class AdminProfileResponse(AvatarMixin, BannerMixin):
80
80
  social_links: Annotated[dict[str, str], Field(default_factory=dict)]
81
81
  profile_visibility: ProfileVisibility
82
82
  date_of_birth: DateOfBirth | None
83
+ follower_count: Annotated[int, Field(ge=0)] = 0
84
+ following_count: Annotated[int, Field(ge=0)] = 0
83
85
 
84
86
 
85
87
  class UserDetailResponse(BaseModel):
@@ -4,7 +4,17 @@ from core_framework.domains.comment import CommentUpdate
4
4
 
5
5
  def comment_update_from_request(*, request: UpdateCommentRequest) -> CommentUpdate:
6
6
  fields_set = frozenset(request.model_fields_set)
7
+
8
+ content = None
9
+ if "content" in fields_set and request.content is not None:
10
+ content = request.content.root
11
+
12
+ mentions = None
13
+ if "mentions" in fields_set:
14
+ mentions = None if request.mentions is None else [mention.root for mention in request.mentions]
15
+
7
16
  return CommentUpdate(
8
17
  fields_set=fields_set,
9
- content=request.content.root if "content" in fields_set and request.content is not None else None,
18
+ content=content,
19
+ mentions=mentions,
10
20
  )
@@ -12,6 +12,14 @@ from core_framework.domains.moderation import ReportCategory
12
12
 
13
13
  class UpdateCommentRequest(BasePatchRequest):
14
14
  content: CommentContent | None = None
15
+ mentions: Annotated[list[UserID], Field(max_length=MAX_MENTIONS_PER_REQUEST)] | None = None
16
+
17
+ @field_validator("mentions", mode="after")
18
+ @classmethod
19
+ def dedupe_mentions(cls, v: list[UserID] | None) -> list[UserID] | None:
20
+ if v is None:
21
+ return None
22
+ return dedupe_mention_user_ids(v)
15
23
 
16
24
 
17
25
  class CommentReportRequest(BaseModel):
@@ -28,6 +28,7 @@ class CommentResponse(BaseModel):
28
28
  id: ULID
29
29
  author: UserReference
30
30
  content: CommentContent
31
+ mentions: list[UserReference]
31
32
  stats: CommentStatsResponse
32
33
  can_reply: bool
33
34
  engagement_allowed: bool
@@ -0,0 +1,28 @@
1
+ from core_framework.api.posts.authenticated.schemas import UpdatePostRequest
2
+ from core_framework.domains.post import PostUpdate
3
+
4
+
5
+ def post_update_from_request(*, request: UpdatePostRequest) -> PostUpdate:
6
+ fields_set = frozenset(request.model_fields_set)
7
+
8
+ content = None
9
+ if "content" in fields_set and request.content is not None:
10
+ content = request.content.root
11
+
12
+ visibility = request.visibility if "visibility" in fields_set else None
13
+
14
+ hashtags = None
15
+ if "hashtags" in fields_set:
16
+ hashtags = None if request.hashtags is None else [hashtag.root for hashtag in request.hashtags]
17
+
18
+ mentions = None
19
+ if "mentions" in fields_set:
20
+ mentions = None if request.mentions is None else [mention.root for mention in request.mentions]
21
+
22
+ return PostUpdate(
23
+ fields_set=fields_set,
24
+ content=content,
25
+ visibility=visibility,
26
+ hashtags=hashtags,
27
+ mentions=mentions,
28
+ )
@@ -37,6 +37,7 @@ class UpdatePostRequest(BasePatchRequest):
37
37
  content: PostContent | None = None
38
38
  visibility: PostVisibility | None = None
39
39
  hashtags: Annotated[list[Hashtag], Field(max_length=MAX_HASHTAGS)] | None = None
40
+ mentions: Annotated[list[UserID], Field(max_length=MAX_MENTIONS_PER_REQUEST)] | None = None
40
41
 
41
42
  @field_validator("hashtags", mode="after")
42
43
  @classmethod
@@ -44,3 +45,10 @@ class UpdatePostRequest(BasePatchRequest):
44
45
  if v is None:
45
46
  return None
46
47
  return list({h.root: h for h in v}.values())
48
+
49
+ @field_validator("mentions", mode="after")
50
+ @classmethod
51
+ def dedupe_mentions(cls, v: list[UserID] | None) -> list[UserID] | None:
52
+ if v is None:
53
+ return None
54
+ return dedupe_mention_user_ids(v)
@@ -31,6 +31,7 @@ class PostResponse(BaseModel):
31
31
  author: UserReference
32
32
  content: PostContent
33
33
  hashtags: list[Hashtag]
34
+ mentions: list[UserReference]
34
35
  visibility: PostVisibility
35
36
  stats: PostStatsResponse
36
37
  engagement_allowed: bool
@@ -41,7 +41,7 @@ from core_framework.application.users.authenticated_service import (
41
41
  change_my_profile,
42
42
  delete_my_current_appeal,
43
43
  follow_user,
44
- mark_user_stats_dirty,
44
+ mark_follow_counterparty_stats_stale,
45
45
  remove_user_report,
46
46
  retrieve_my_account,
47
47
  retrieve_my_appeals,
@@ -446,8 +446,11 @@ async def put_follow_user(
446
446
  validated_target_user_id = validate_user_id(user_id=target_user_id)
447
447
  changed = await follow_user(user_id=user_id.root, target_user_id=validated_target_user_id.root)
448
448
  if changed:
449
- background_tasks.add_task(mark_user_stats_dirty, user_id=user_id.root)
450
- background_tasks.add_task(mark_user_stats_dirty, user_id=validated_target_user_id.root)
449
+ background_tasks.add_task(
450
+ mark_follow_counterparty_stats_stale,
451
+ user_id=user_id.root,
452
+ target_user_id=validated_target_user_id.root,
453
+ )
451
454
 
452
455
 
453
456
  @router.delete(
@@ -463,8 +466,11 @@ async def delete_follow_user(
463
466
  validated_target_user_id = validate_user_id(user_id=target_user_id)
464
467
  changed = await unfollow_user(user_id=user_id.root, target_user_id=validated_target_user_id.root)
465
468
  if changed:
466
- background_tasks.add_task(mark_user_stats_dirty, user_id=user_id.root)
467
- background_tasks.add_task(mark_user_stats_dirty, user_id=validated_target_user_id.root)
469
+ background_tasks.add_task(
470
+ mark_follow_counterparty_stats_stale,
471
+ user_id=user_id.root,
472
+ target_user_id=validated_target_user_id.root,
473
+ )
468
474
 
469
475
 
470
476
  @router.get(
@@ -48,6 +48,8 @@ class ProfileResponse(AvatarMixin, BannerMixin):
48
48
  social_links: dict[str, str]
49
49
  profile_visibility: ProfileVisibility
50
50
  date_of_birth: DateOfBirth | None
51
+ follower_count: Annotated[int, Field(ge=0)] = 0
52
+ following_count: Annotated[int, Field(ge=0)] = 0
51
53
 
52
54
 
53
55
  class AccountResponse(AvatarMixin):
@@ -4,9 +4,10 @@ from fastapi import APIRouter, Query
4
4
  from fastapi.exceptions import RequestValidationError
5
5
  from pydantic import ValidationError
6
6
 
7
- from core_framework.api.users.public.schemas import UsernameExistsResponse
8
- from core_framework.api.users.shared.schemas import Username
9
- from core_framework.application.users.public_service import is_username_available
7
+ from core_framework.api.dependencies import OptionalUserID
8
+ from core_framework.api.users.public.schemas import UsernameExistsResponse, UserProfileResponse
9
+ from core_framework.api.users.shared.schemas import Username, validate_user_id
10
+ from core_framework.application.users.public_service import is_username_available, retrieve_user_profile
10
11
 
11
12
  router = APIRouter(
12
13
  prefix="/users",
@@ -23,3 +24,12 @@ async def get_username_exists(username: Annotated[str, Query()]) -> Any:
23
24
  except ValidationError as e:
24
25
  raise RequestValidationError(e.errors()) from e
25
26
  return {"status": "available" if await is_username_available(username=validated_username.root) else "taken"}
27
+
28
+
29
+ @router.get("/{target_user_id}/profile", response_model=UserProfileResponse)
30
+ async def get_user_profile(target_user_id: str, viewer_id: OptionalUserID) -> Any:
31
+ validated_target_user_id = validate_user_id(user_id=target_user_id)
32
+ return await retrieve_user_profile(
33
+ target_user_id=validated_target_user_id.root,
34
+ viewer_id=viewer_id.root if viewer_id else None,
35
+ )
@@ -0,0 +1,33 @@
1
+ from typing import Annotated, Literal
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+ from core_framework.api.users.shared.schemas import (
6
+ AvatarMixin,
7
+ BannerMixin,
8
+ Bio,
9
+ DisplayName,
10
+ UserID,
11
+ Username,
12
+ UserStatus,
13
+ )
14
+
15
+
16
+ class UsernameExistsResponse(BaseModel):
17
+ status: Literal["available", "taken"]
18
+
19
+
20
+ class UserProfileViewerContext(BaseModel):
21
+ is_following: bool
22
+
23
+
24
+ class UserProfileResponse(AvatarMixin, BannerMixin):
25
+ user_id: UserID
26
+ username: Username
27
+ display_name: DisplayName | None = None
28
+ bio: Bio | None = None
29
+ status: UserStatus | None = None
30
+ social_links: dict[str, str]
31
+ follower_count: Annotated[int, Field(ge=0)] = 0
32
+ following_count: Annotated[int, Field(ge=0)] = 0
33
+ viewer_context: UserProfileViewerContext | None = None
@@ -15,16 +15,11 @@ from core_framework.domains.user import UserIdentity
15
15
  condition=lambda result, args, kwargs, key=None: result.identity != UserIdentity.DEFAULT,
16
16
  )
17
17
  async def get_user_detail(*, user_id: str) -> UserDetail:
18
- """Return cached user identity and moderation state for auth middleware.
18
+ """
19
+ Return cached user identity and moderation state for auth middleware.
19
20
 
20
21
  Fetches identity and moderation mappings concurrently. Results are cached
21
22
  under ``CacheKey.USER_DETAIL``; default (missing) identities are not cached.
22
-
23
- Args:
24
- user_id: External user ID from the auth token.
25
-
26
- Returns:
27
- Combined identity and moderation snapshot for access checks.
28
23
  """
29
24
  async with asyncio.TaskGroup() as tg:
30
25
  identity_task = tg.create_task(user_deps.user_service.retrieve_user_identity_mapping(user_ids={user_id}))
@@ -6,14 +6,7 @@ from core_framework.domains.user import CreatedUser
6
6
 
7
7
 
8
8
  async def register_user(*, user_id: str) -> CreatedUser:
9
- """Register a new user and prime username availability cache.
10
-
11
- Args:
12
- user_id: External identity for the new account.
13
-
14
- Returns:
15
- Created user ID and assigned username.
16
- """
9
+ """Register a new user and prime username availability cache."""
17
10
  created_user = await user_deps.user_service.add_user(user_id=user_id)
18
11
  await cache.set(
19
12
  CacheKey.TAKEN_USERNAMES.format_key(username=created_user.username),
@@ -10,6 +10,7 @@ from core_framework.domains.user.dependencies import configure_user_dependencies
10
10
 
11
11
 
12
12
  def configure_application_dependencies(*, runtime: CoreRuntime) -> None:
13
+ """Wire application-level dependencies from the shared runtime."""
13
14
  configure_worker_jobs(runtime=runtime)
14
15
  configure_cashews_backend(redis_cache_url=runtime.settings.redis.redis_cache_url)
15
16
  configure_comment_dependencies(runtime)
@@ -9,14 +9,11 @@ logger = get_logger(__name__)
9
9
 
10
10
 
11
11
  async def invalidate_cache_targets(*, actor_id: str, items: list[CacheInvalidateTarget]) -> None:
12
- """Invalidate cashews cache entries requested by an admin.
12
+ """
13
+ Invalidate cashews cache entries requested by an admin.
13
14
 
14
15
  Best-effort: failures are logged and do not propagate. Callers receive no
15
16
  signal when Redis or cashews is unavailable (see admin cache invalidation flow doc).
16
-
17
- Args:
18
- actor_id: Firebase uid of the admin performing the operation.
19
- items: Cache keys and parameters to invalidate.
20
17
  """
21
18
  for item in items:
22
19
  try:
@@ -39,12 +36,7 @@ async def invalidate_cache_targets(*, actor_id: str, items: list[CacheInvalidate
39
36
 
40
37
 
41
38
  async def enqueue_cache_pattern_invalidation(*, actor_id: str, target: CacheInvalidatePatternTarget) -> None:
42
- """Enqueue a worker job to invalidate cache entries matching a partial pattern.
43
-
44
- Args:
45
- actor_id: Firebase uid of the admin performing the operation.
46
- target: Cache resource and partial key parameters (omitted fields wildcard).
47
- """
39
+ """Enqueue a worker job to invalidate cache entries matching a partial pattern."""
48
40
  await worker_jobs.enqueue_job(
49
41
  job_name=WorkerJobName.PROCESS_CACHE_PATTERN_INVALIDATION,
50
42
  actor_id=actor_id,
@@ -54,15 +46,7 @@ async def enqueue_cache_pattern_invalidation(*, actor_id: str, target: CacheInva
54
46
 
55
47
 
56
48
  async def invalidate_cache_pattern_target(*, actor_id: str, target: CacheInvalidatePatternTarget) -> None:
57
- """Invalidate cashews cache entries matching a partial pattern (worker entrypoint).
58
-
59
- Args:
60
- actor_id: Firebase uid of the admin who requested the invalidation.
61
- target: Cache resource and partial key parameters (omitted fields wildcard).
62
-
63
- Raises:
64
- Exception: Re-raised after logging so the worker can retry transient failures.
65
- """
49
+ """Invalidate cashews cache entries matching a partial pattern (worker entrypoint)."""
66
50
  try:
67
51
  await invalidate_cache(target.resource, **target.params)
68
52
  except Exception: