core-framework 1.3.0__tar.gz → 1.4.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 (372) hide show
  1. {core_framework-1.3.0 → core_framework-1.4.0}/.cursor/rules/constants-final.mdc +1 -1
  2. {core_framework-1.3.0 → core_framework-1.4.0}/.cursor/rules/database-triggers.md +1 -24
  3. {core_framework-1.3.0 → core_framework-1.4.0}/.cursor/rules/domain-input-guards.mdc +3 -2
  4. {core_framework-1.3.0 → core_framework-1.4.0}/.dockerignore +2 -3
  5. {core_framework-1.3.0 → core_framework-1.4.0}/.github/workflows/_deploy.yml +2 -2
  6. {core_framework-1.3.0 → core_framework-1.4.0}/CHANGELOG.md +31 -1
  7. {core_framework-1.3.0 → core_framework-1.4.0}/PKG-INFO +1 -1
  8. core_framework-1.4.0/alembic/user/alembic/versions/user_add_date_of_birth_to_profiles.py +27 -0
  9. {core_framework-1.3.0 → core_framework-1.4.0}/config.toml +2 -2
  10. {core_framework-1.3.0 → core_framework-1.4.0}/config.toml.template +2 -2
  11. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/admin/users/router.py +2 -2
  12. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/admin/users/schemas.py +8 -5
  13. core_framework-1.4.0/core_framework/api/comments/authenticated/mappers.py +10 -0
  14. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/comments/authenticated/router.py +2 -2
  15. core_framework-1.4.0/core_framework/api/posts/authenticated/mappers.py +14 -0
  16. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/posts/authenticated/router.py +2 -2
  17. core_framework-1.4.0/core_framework/api/users/authenticated/mappers.py +21 -0
  18. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/users/authenticated/router.py +17 -6
  19. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/users/authenticated/schemas.py +3 -0
  20. core_framework-1.4.0/core_framework/api/users/mappers.py +77 -0
  21. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/users/shared/schemas.py +35 -6
  22. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/comments/authenticated_service.py +3 -9
  23. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/posts/authenticated_service.py +3 -9
  24. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/users/admin_service.py +17 -12
  25. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/users/authenticated_service.py +19 -23
  26. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/core/settings.py +16 -3
  27. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/comment/__init__.py +2 -0
  28. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/comment/models.py +30 -1
  29. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/comment/repository.py +4 -5
  30. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/comment/service.py +7 -8
  31. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/post/__init__.py +2 -1
  32. core_framework-1.4.0/core_framework/domains/post/models.py +98 -0
  33. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/post/repository.py +5 -12
  34. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/post/service.py +7 -12
  35. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/user/__init__.py +6 -0
  36. core_framework-1.4.0/core_framework/domains/user/constants.py +11 -0
  37. core_framework-1.4.0/core_framework/domains/user/models.py +269 -0
  38. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/user/repository.py +23 -25
  39. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/user/service.py +14 -38
  40. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/utils.py +0 -13
  41. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/main.py +2 -2
  42. {core_framework-1.3.0 → core_framework-1.4.0}/dockerfile +1 -1
  43. {core_framework-1.3.0 → core_framework-1.4.0}/docs/conventions.md +8 -1
  44. core_framework-1.4.0/docs/flows/media/upload_pipeline.md +454 -0
  45. core_framework-1.4.0/docs/flows/users/avatar.md +91 -0
  46. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/users/change_history.md +2 -1
  47. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/users/profile.md +10 -11
  48. core_framework-1.4.0/docs/patch-update-typed-payload.md +186 -0
  49. {core_framework-1.3.0 → core_framework-1.4.0}/docs/todo.md +1 -0
  50. {core_framework-1.3.0 → core_framework-1.4.0}/pyproject.toml +1 -1
  51. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/admin/users/router_test.py +42 -2
  52. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/users/authenticated/router_test.py +90 -12
  53. {core_framework-1.3.0 → core_framework-1.4.0}/uv.lock +1 -1
  54. core_framework-1.3.0/core_framework/domains/post/models.py +0 -61
  55. core_framework-1.3.0/core_framework/domains/user/constants.py +0 -41
  56. core_framework-1.3.0/core_framework/domains/user/models.py +0 -153
  57. {core_framework-1.3.0 → core_framework-1.4.0}/.cursor/rules/api-layer.mdc +0 -0
  58. {core_framework-1.3.0 → core_framework-1.4.0}/.cursor/rules/api-reference-docs.mdc +0 -0
  59. {core_framework-1.3.0 → core_framework-1.4.0}/.cursor/rules/api-security.mdc +0 -0
  60. {core_framework-1.3.0 → core_framework-1.4.0}/.cursor/rules/api-validation.mdc +0 -0
  61. {core_framework-1.3.0 → core_framework-1.4.0}/.cursor/rules/application-layer.mdc +0 -0
  62. {core_framework-1.3.0 → core_framework-1.4.0}/.cursor/rules/domain-caller-context.mdc +0 -0
  63. {core_framework-1.3.0 → core_framework-1.4.0}/.cursor/rules/domain-imports.mdc +0 -0
  64. {core_framework-1.3.0 → core_framework-1.4.0}/.cursor/rules/domain-repository-exceptions.mdc +0 -0
  65. {core_framework-1.3.0 → core_framework-1.4.0}/.cursor/rules/flow-documentation.mdc +0 -0
  66. {core_framework-1.3.0 → core_framework-1.4.0}/.cursor/rules/implementation-workflow.mdc +0 -0
  67. {core_framework-1.3.0 → core_framework-1.4.0}/.cursor/rules/integration-test-strategy.mdc +0 -0
  68. {core_framework-1.3.0 → core_framework-1.4.0}/.cursor/rules/layer-boundaries.mdc +0 -0
  69. {core_framework-1.3.0 → core_framework-1.4.0}/.cursor/rules/no-code-in-docs.mdc +0 -0
  70. {core_framework-1.3.0 → core_framework-1.4.0}/.cursor/rules/no-docstrings.mdc +0 -0
  71. {core_framework-1.3.0 → core_framework-1.4.0}/.cursor/rules/postgres-config-conventions.mdc +0 -0
  72. {core_framework-1.3.0 → core_framework-1.4.0}/.cursor/rules/repository-read-consistency.mdc +0 -0
  73. {core_framework-1.3.0 → core_framework-1.4.0}/.cursor/rules/strong-read-opt-in.mdc +0 -0
  74. {core_framework-1.3.0 → core_framework-1.4.0}/.cursor/rules/tech-stack.mdc +0 -0
  75. {core_framework-1.3.0 → core_framework-1.4.0}/.cursor/skills/add-config/SKILL.md +0 -0
  76. {core_framework-1.3.0 → core_framework-1.4.0}/.cursor/skills/add-domain/SKILL.md +0 -0
  77. {core_framework-1.3.0 → core_framework-1.4.0}/.cursor/skills/code-review/SKILL.md +0 -0
  78. {core_framework-1.3.0 → core_framework-1.4.0}/.cursor/skills/recommend-features/SKILL.md +0 -0
  79. {core_framework-1.3.0 → core_framework-1.4.0}/.github/workflows/dev-ci-cd.yaml +0 -0
  80. {core_framework-1.3.0 → core_framework-1.4.0}/.github/workflows/manual-deployment.yaml +0 -0
  81. {core_framework-1.3.0 → core_framework-1.4.0}/.github/workflows/publish-pypi.yml +0 -0
  82. {core_framework-1.3.0 → core_framework-1.4.0}/.github/workflows/test.yaml +0 -0
  83. {core_framework-1.3.0 → core_framework-1.4.0}/.gitignore +0 -0
  84. {core_framework-1.3.0 → core_framework-1.4.0}/.pre-commit-config.yaml +0 -0
  85. {core_framework-1.3.0 → core_framework-1.4.0}/.python-version +0 -0
  86. {core_framework-1.3.0 → core_framework-1.4.0}/LICENSE +0 -0
  87. {core_framework-1.3.0 → core_framework-1.4.0}/README.md +0 -0
  88. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/comment/alembic/README +0 -0
  89. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/comment/alembic/env.py +0 -0
  90. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/comment/alembic/script.py.mako +0 -0
  91. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/comment/alembic/versions/v1_comment_init_baseline.py +0 -0
  92. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/comment/alembic.ini +0 -0
  93. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/extension/alembic/README +0 -0
  94. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/extension/alembic/env.py +0 -0
  95. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/extension/alembic/script.py.mako +0 -0
  96. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/extension/alembic/versions/v1_ext_init_baseline.py +0 -0
  97. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/extension/alembic.ini +0 -0
  98. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/moderation/alembic/README +0 -0
  99. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/moderation/alembic/env.py +0 -0
  100. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/moderation/alembic/script.py.mako +0 -0
  101. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/moderation/alembic/versions/v1_mod_init_baseline.py +0 -0
  102. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/moderation/alembic.ini +0 -0
  103. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/notification/alembic/README +0 -0
  104. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/notification/alembic/env.py +0 -0
  105. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/notification/alembic/script.py.mako +0 -0
  106. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/notification/alembic/versions/v1_notif_init_baseline.py +0 -0
  107. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/notification/alembic.ini +0 -0
  108. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/post/alembic/README +0 -0
  109. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/post/alembic/env.py +0 -0
  110. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/post/alembic/script.py.mako +0 -0
  111. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/post/alembic/versions/v1_post_init_baseline.py +0 -0
  112. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/post/alembic.ini +0 -0
  113. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/user/alembic/README +0 -0
  114. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/user/alembic/env.py +0 -0
  115. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/user/alembic/script.py.mako +0 -0
  116. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/user/alembic/versions/v1_user_init_baseline.py +0 -0
  117. {core_framework-1.3.0 → core_framework-1.4.0}/alembic/user/alembic.ini +0 -0
  118. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/__init__.py +0 -0
  119. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/__init__.py +0 -0
  120. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/admin/__init__.py +0 -0
  121. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/admin/comments/router.py +0 -0
  122. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/admin/comments/schemas.py +0 -0
  123. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/admin/moderation/__init__.py +0 -0
  124. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/admin/moderation/router.py +0 -0
  125. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/admin/moderation/schemas.py +0 -0
  126. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/admin/posts/router.py +0 -0
  127. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/admin/posts/schemas.py +0 -0
  128. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/admin/router.py +0 -0
  129. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/admin/users/__init__.py +0 -0
  130. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/auth/__init__.py +0 -0
  131. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/auth/router.py +0 -0
  132. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/auth/schemas.py +0 -0
  133. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/comments/authenticated/schemas.py +0 -0
  134. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/comments/public/router.py +0 -0
  135. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/comments/public/schemas.py +0 -0
  136. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/comments/router.py +0 -0
  137. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/comments/schemas.py +0 -0
  138. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/constants.py +0 -0
  139. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/dependencies.py +0 -0
  140. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/events/router.py +0 -0
  141. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/events/schemas.py +0 -0
  142. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/notifications/authenticated/router.py +0 -0
  143. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/notifications/authenticated/schemas.py +0 -0
  144. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/notifications/router.py +0 -0
  145. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/posts/authenticated/schemas.py +0 -0
  146. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/posts/public/router.py +0 -0
  147. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/posts/public/schemas.py +0 -0
  148. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/posts/router.py +0 -0
  149. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/posts/schemas.py +0 -0
  150. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/router.py +0 -0
  151. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/schemas.py +0 -0
  152. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/system/__init__.py +0 -0
  153. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/system/router.py +0 -0
  154. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/users/__init__.py +0 -0
  155. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/users/authenticated/__init__.py +0 -0
  156. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/users/public/__init__.py +0 -0
  157. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/users/public/router.py +0 -0
  158. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/users/public/schemas.py +0 -0
  159. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/api/users/router.py +0 -0
  160. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/__init__.py +0 -0
  161. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/auth/__init__.py +0 -0
  162. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/auth/access_service.py +0 -0
  163. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/auth/auth_service.py +0 -0
  164. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/auth/models.py +0 -0
  165. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/bootstrap.py +0 -0
  166. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/comments/admin_service.py +0 -0
  167. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/comments/aggregation_service.py +0 -0
  168. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/comments/public_service.py +0 -0
  169. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/events/README.md +0 -0
  170. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/events/event_service.py +0 -0
  171. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/events/event_token.py +0 -0
  172. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/events/models.py +0 -0
  173. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/moderation/__init__.py +0 -0
  174. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/moderation/appeal_service.py +0 -0
  175. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/moderation/moderator_service.py +0 -0
  176. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/moderation/report_service.py +0 -0
  177. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/moderation/scheduled_service.py +0 -0
  178. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/moderation/user_service.py +0 -0
  179. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/notifications/README.md +0 -0
  180. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/notifications/enums.py +0 -0
  181. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/notifications/inbox_service.py +0 -0
  182. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/notifications/mute_service.py +0 -0
  183. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/notifications/notification_service.py +0 -0
  184. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/posts/admin_service.py +0 -0
  185. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/posts/aggregation_service.py +0 -0
  186. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/posts/public_service.py +0 -0
  187. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/shared/__init__.py +0 -0
  188. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/shared/enums.py +0 -0
  189. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/shared/exceptions.py +0 -0
  190. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/shared/user_agent.py +0 -0
  191. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/users/__init__.py +0 -0
  192. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/users/aggregation_service.py +0 -0
  193. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/users/public_service.py +0 -0
  194. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/application/users/scheduled_service.py +0 -0
  195. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/asgi.py +0 -0
  196. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/bundled_alembic.py +0 -0
  197. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/constants.py +0 -0
  198. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/core/__init__.py +0 -0
  199. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/core/cache.py +0 -0
  200. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/core/context.py +0 -0
  201. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/core/database.py +0 -0
  202. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/core/exception_handlers/__init__.py +0 -0
  203. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/core/exception_handlers/comment.py +0 -0
  204. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/core/exception_handlers/common.py +0 -0
  205. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/core/exception_handlers/moderation.py +0 -0
  206. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/core/exception_handlers/notification.py +0 -0
  207. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/core/exception_handlers/post.py +0 -0
  208. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/core/exception_handlers/setup.py +0 -0
  209. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/core/exception_handlers/user.py +0 -0
  210. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/core/firebase.py +0 -0
  211. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/core/http_client.py +0 -0
  212. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/core/logging.py +0 -0
  213. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/core/middleware.py +0 -0
  214. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/core/observability.py +0 -0
  215. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/core/pagination.py +0 -0
  216. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/core/redis.py +0 -0
  217. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/core/runtime.py +0 -0
  218. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/__init__.py +0 -0
  219. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/comment/README.md +0 -0
  220. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/comment/constants.py +0 -0
  221. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/comment/dependencies.py +0 -0
  222. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/comment/enums.py +0 -0
  223. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/comment/exceptions.py +0 -0
  224. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/exceptions.py +0 -0
  225. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/moderation/README.md +0 -0
  226. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/moderation/__init__.py +0 -0
  227. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/moderation/dependencies.py +0 -0
  228. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/moderation/enums.py +0 -0
  229. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/moderation/exceptions.py +0 -0
  230. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/moderation/models.py +0 -0
  231. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/moderation/repository.py +0 -0
  232. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/moderation/service.py +0 -0
  233. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/notification/README.md +0 -0
  234. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/notification/__init__.py +0 -0
  235. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/notification/dependencies.py +0 -0
  236. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/notification/enums.py +0 -0
  237. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/notification/exceptions.py +0 -0
  238. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/notification/models.py +0 -0
  239. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/notification/repository.py +0 -0
  240. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/notification/service.py +0 -0
  241. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/post/README.md +0 -0
  242. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/post/constants.py +0 -0
  243. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/post/dependencies.py +0 -0
  244. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/post/enums.py +0 -0
  245. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/post/exceptions.py +0 -0
  246. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/user/README.md +0 -0
  247. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/user/dependencies.py +0 -0
  248. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/user/enums.py +0 -0
  249. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/user/exceptions.py +0 -0
  250. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/domains/user/utils.py +0 -0
  251. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/migrate_cli.py +0 -0
  252. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/testing/__init__.py +0 -0
  253. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/testing/arq.py +0 -0
  254. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/testing/auth.py +0 -0
  255. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/testing/config.py +0 -0
  256. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/testing/containers.py +0 -0
  257. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/testing/firebase.py +0 -0
  258. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/testing/hookspecs.py +0 -0
  259. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/testing/httpx_test_client.py +0 -0
  260. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/testing/migrations.py +0 -0
  261. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/testing/plugin.py +0 -0
  262. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/worker/__init__.py +0 -0
  263. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/worker/main.py +0 -0
  264. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/worker/schedules/__init__.py +0 -0
  265. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/worker/schedules/schedule_aggregate_comment_stats.py +0 -0
  266. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/worker/schedules/schedule_aggregate_post_stats.py +0 -0
  267. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/worker/schedules/schedule_aggregate_user_stats.py +0 -0
  268. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/worker/schedules/schedule_expired_account_deletions.py +0 -0
  269. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/worker/schedules/schedule_expired_mute_lifts.py +0 -0
  270. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/worker/tasks/__init__.py +0 -0
  271. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/worker/tasks/process_account_deletion.py +0 -0
  272. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/worker/tasks/process_aggregate_comment_stats.py +0 -0
  273. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/worker/tasks/process_aggregate_post_stats.py +0 -0
  274. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/worker/tasks/process_aggregate_user_stats.py +0 -0
  275. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/worker/tasks/process_mute_lift.py +0 -0
  276. {core_framework-1.3.0 → core_framework-1.4.0}/core_framework/worker/worker_context.py +0 -0
  277. {core_framework-1.3.0 → core_framework-1.4.0}/docker-compose.dev.yaml +0 -0
  278. {core_framework-1.3.0 → core_framework-1.4.0}/docker-compose.yml +0 -0
  279. {core_framework-1.3.0 → core_framework-1.4.0}/docs/api.md +0 -0
  280. {core_framework-1.3.0 → core_framework-1.4.0}/docs/architecture-decisions.md +0 -0
  281. {core_framework-1.3.0 → core_framework-1.4.0}/docs/architecture.md +0 -0
  282. {core_framework-1.3.0 → core_framework-1.4.0}/docs/core-framework-migration.md +0 -0
  283. {core_framework-1.3.0 → core_framework-1.4.0}/docs/database-triggers.md +0 -0
  284. {core_framework-1.3.0 → core_framework-1.4.0}/docs/event-outbox-design.md +0 -0
  285. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/auth/access_control.md +0 -0
  286. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/auth/registration.md +0 -0
  287. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/comments/admin_comments.md +0 -0
  288. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/comments/comment_report.md +0 -0
  289. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/comments/comment_stats_aggregation.md +0 -0
  290. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/comments/create_comment.md +0 -0
  291. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/comments/delete_comment.md +0 -0
  292. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/comments/edit_comment.md +0 -0
  293. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/comments/retrieve_comments.md +0 -0
  294. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/events/events.md +0 -0
  295. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/mentions/mentions_in_content.md +0 -0
  296. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/moderation/appeals.md +0 -0
  297. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/moderation/internal_notes.md +0 -0
  298. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/moderation/moderator_actions.md +0 -0
  299. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/moderation/reports.md +0 -0
  300. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/moderation/restrictions.md +0 -0
  301. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/notifications/notification_inbox.md +0 -0
  302. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/posts/admin_posts.md +0 -0
  303. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/posts/author_context.md +0 -0
  304. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/posts/hashtag_discovery.md +0 -0
  305. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/posts/post_like.md +0 -0
  306. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/posts/post_stats_aggregation.md +0 -0
  307. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/users/account.md +0 -0
  308. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/users/account_deletion.md +0 -0
  309. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/users/blocks.md +0 -0
  310. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/users/check_username_exists.md +0 -0
  311. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/users/follow.md +0 -0
  312. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/users/my_posts_and_comments.md +0 -0
  313. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/users/preferences.md +0 -0
  314. {core_framework-1.3.0 → core_framework-1.4.0}/docs/flows/users/user_removal.md +0 -0
  315. {core_framework-1.3.0 → core_framework-1.4.0}/docs/follow-system-design.md +0 -0
  316. {core_framework-1.3.0 → core_framework-1.4.0}/docs/package-api.md +0 -0
  317. {core_framework-1.3.0 → core_framework-1.4.0}/docs/testing-plugin-design.md +0 -0
  318. {core_framework-1.3.0 → core_framework-1.4.0}/firebase_config.example.json +0 -0
  319. {core_framework-1.3.0 → core_framework-1.4.0}/makefile +0 -0
  320. {core_framework-1.3.0 → core_framework-1.4.0}/tests/__init__.py +0 -0
  321. {core_framework-1.3.0 → core_framework-1.4.0}/tests/conftest.py +0 -0
  322. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/__init__.py +0 -0
  323. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/__init__.py +0 -0
  324. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/_http_helpers.py +0 -0
  325. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/admin/__init__.py +0 -0
  326. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/admin/comments/__init__.py +0 -0
  327. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/admin/comments/router_test.py +0 -0
  328. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/admin/moderation/__init__.py +0 -0
  329. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/admin/moderation/router_test.py +0 -0
  330. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/admin/posts/__init__.py +0 -0
  331. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/admin/posts/router_test.py +0 -0
  332. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/admin/users/__init__.py +0 -0
  333. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/auth/__init__.py +0 -0
  334. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/auth/router_test.py +0 -0
  335. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/comments/__init__.py +0 -0
  336. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/comments/authenticated/comment_writes_integration_test.py +0 -0
  337. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/comments/public/__init__.py +0 -0
  338. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/comments/public/router_test.py +0 -0
  339. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/events/router_test.py +0 -0
  340. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/notifications/__init__.py +0 -0
  341. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/notifications/router_test.py +0 -0
  342. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/posts/__init__.py +0 -0
  343. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/posts/authenticated/post_writes_integration_test.py +0 -0
  344. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/posts/comment_count_aggregation_test.py +0 -0
  345. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/posts/followers_visibility_test.py +0 -0
  346. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/posts/post_stats_dirty_marking_test.py +0 -0
  347. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/posts/public/__init__.py +0 -0
  348. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/posts/public/router_test.py +0 -0
  349. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/system/__init__.py +0 -0
  350. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/system/router_test.py +0 -0
  351. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/users/__init__.py +0 -0
  352. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/users/authenticated/__init__.py +0 -0
  353. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/users/public/__init__.py +0 -0
  354. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/api/users/public/router_test.py +0 -0
  355. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/worker/__init__.py +0 -0
  356. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/worker/account_deletion_test.py +0 -0
  357. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/worker/aggregate_comment_stats_test.py +0 -0
  358. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/worker/aggregate_post_stats_test.py +0 -0
  359. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/worker/aggregate_user_stats_test.py +0 -0
  360. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/worker/conftest.py +0 -0
  361. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/worker/mute_lift_test.py +0 -0
  362. {core_framework-1.3.0 → core_framework-1.4.0}/tests/integration/worker/utils_test.py +0 -0
  363. {core_framework-1.3.0 → core_framework-1.4.0}/tests/unit/__init__.py +0 -0
  364. {core_framework-1.3.0 → core_framework-1.4.0}/tests/unit/application/comments/__init__.py +0 -0
  365. {core_framework-1.3.0 → core_framework-1.4.0}/tests/unit/application/events/event_service_test.py +0 -0
  366. {core_framework-1.3.0 → core_framework-1.4.0}/tests/unit/application/notifications/inbox_service_test.py +0 -0
  367. {core_framework-1.3.0 → core_framework-1.4.0}/tests/unit/core/bundled_alembic_test.py +0 -0
  368. {core_framework-1.3.0 → core_framework-1.4.0}/tests/unit/core/migrate_cli_test.py +0 -0
  369. {core_framework-1.3.0 → core_framework-1.4.0}/tests/unit/core/pagination_test.py +0 -0
  370. {core_framework-1.3.0 → core_framework-1.4.0}/tests/unit/domains/__init__.py +0 -0
  371. {core_framework-1.3.0 → core_framework-1.4.0}/tests/unit/domains/comment/__init__.py +0 -0
  372. {core_framework-1.3.0 → core_framework-1.4.0}/tests/unit/domains/user/service_test.py +0 -0
@@ -9,7 +9,7 @@ alwaysApply: false
9
9
  When introducing or editing **module-level constants** (values that are fixed for the lifetime of the module and not meant to be reassigned):
10
10
 
11
11
  - Annotate them with **`typing.Final`** and an explicit type, e.g. **`NAME: Final[int] = 42`** or **`_INTERNAL: Final[str] = "henry"`**.
12
- - Prefer **`FrozenSet`**, **`frozenset` literals**, **`MappingProxyType`**, or immutable collections for grouped constants when mutation must be prevented — e.g. **`USER_PREFERENCE_UPDATE_COLUMNS`** in **`core_framework/domains/user/constants.py`**.
12
+ - Prefer **`FrozenSet`**, **`frozenset` literals**, **`MappingProxyType`**, or immutable collections for grouped constants when mutation must be prevented — e.g. **`ProfileUpdate.UPDATE_COLUMNS`** on domain update types.
13
13
  - **`Final`** is for **assignment** semantics (no rebinding): it does not freeze mutable object contents unless the type/container is immutable.
14
14
 
15
15
  Do not refactor unrelated legacy constants only to add **`Final`** unless you are already touching that symbol.
@@ -124,30 +124,7 @@ $$ language plpgsql;
124
124
  - Error message isn't localized
125
125
  - Makes assumptions about business rules
126
126
 
127
- **Correct approach:**
128
-
129
- ```python
130
- # In service layer
131
- async def change_profile(
132
- self,
133
- *,
134
- actor_id: str,
135
- user_id: str,
136
- validated_update_request: dict[str, Any],
137
- ) -> None:
138
- if "username" in validated_update_request:
139
- recent_changes = await self.repository.select_recent_username_changes(
140
- user_id=user_id, since=datetime.now(timezone.utc) - timedelta(days=7)
141
- )
142
- if recent_changes:
143
- raise UsernameCooldownException()
144
-
145
- await self.repository.update_profile(
146
- user_id=user_id,
147
- validated_update_request=validated_update_request,
148
- actor_id=actor_id,
149
- )
150
- ```
127
+ **Correct approach:** enforce cooldown (or other policy) in the service, then call the repository with a typed **`ProfileUpdate`** (or the relevant `*Update`).
151
128
 
152
129
  ### ❌ Bad: Automatic State Transitions
153
130
 
@@ -43,13 +43,14 @@ Use these across domains so validation stays consistent and call sites stay read
43
43
  | **`validate_pagination_limit(limit=...)`** | Paginated reads: `limit` in `[1, MAX_PAGE_FETCH_SIZE]` (aligned with `core_framework.core.pagination`). |
44
44
  | **`validate_pagination_offset(offset=...)`** | Paginated reads: `offset` must be **≥ 0** (aligned with `OffsetQueryParams`). |
45
45
  | **`require_positive_int(value=..., field=...)`** | **Integer** surrogate keys only (e.g. moderation `report_id`, `appeal_id`, `note_id`). Must be **≥ 1**. Domains that identify entities with **string** IDs (ULIDs) use **`require_non_blank_*`** instead—do not add this helper there. |
46
- | **`require_validated_update_has_allowed_fields(validated_update_request=..., allowed_fields=...)`** | PATCH-style dict updates: reject empty bodies and bodies with **no** recognized keys (use domain constants such as **`USER_*_UPDATE_FIELDS`**). |
47
46
 
48
47
  Prefer **`require_non_blank_kwargs`** / **`require_non_blank_if_not_none`** over many repeated **`require_non_blank`** lines when a method checks several fields.
49
48
 
49
+ **PATCH partial updates:** domain `*Update` dataclasses (e.g. **`ProfileUpdate`**) expose **`require_has_updates()`** (non-empty body with at least one allowed field) and **`fields_set`** (which keys the client sent). API mappers build these from Pydantic **`model_fields_set`**; services call **`update.require_has_updates()`** before repository writes. Empty PATCH bodies remain rejected at the API via **`BasePatchRequest`**.
50
+
50
51
  ## Repository (thin data access; no duplicate validation)
51
52
 
52
- - **Call path**: API and application code must not import repositories directly (see **`domain-imports.mdc`**); they use **`{domain}_service`** from **`dependencies.py`**. So e.g. **`UserRepository.update_preferences` / `update_profile` / `update_account`** are only reached through **`UserService`**, which runs **`require_validated_update_has_allowed_fields`**—there is no supported bypass for empty or all-unknown PATCH bodies in product code.
53
+ - **Call path**: API and application code must not import repositories directly (see **`domain-imports.mdc`**); they use **`{domain}_service`** from **`dependencies.py`**. PATCH flows reach **`UserRepository.update_*`** (and post/comment equivalents) only through **`UserService.change_*`**, which runs **`require_has_updates()`** on the typed update—there is no supported bypass for empty or all-unknown PATCH bodies in product code.
53
54
  - **Prefer no input-validation guards** in repositories (empty batches, non-blank strings, pagination limits, positive int IDs): the **domain service** owns those contracts. The repository runs SQL; **`WHERE … = any($1)`** with an empty array is valid in Postgres and returns no rows.
54
55
  - Only add a repository guard for **SQL-specific** hazards that are awkward to express as service policy (rare); **do not** duplicate service-level “skip if empty” logic.
55
56
  - **Do not** use the repository as the **main** home for product rules that belong in the service.
@@ -25,9 +25,8 @@ htmlcov/
25
25
  *.swo
26
26
  *~
27
27
 
28
- # Documentation
29
- README.md
30
- *.md
28
+ # Documentation (wheel build needs root README.md; skip large doc tree)
29
+ docs/
31
30
 
32
31
  # CI/CD
33
32
  .github/
@@ -39,8 +39,8 @@ jobs:
39
39
  REDIS_HOST: ${{ secrets.REDIS_HOST }}
40
40
  REDIS_PORT: ${{ secrets.REDIS_PORT }}
41
41
  ALLOWED_HOSTS: ${{ secrets.ALLOWED_HOSTS }}
42
- AVATAR_BASE_URL: ${{ secrets.AVATAR_BASE_URL }}
43
- AVATAR_DEFAULT_URL: ${{ secrets.AVATAR_DEFAULT_URL }}
42
+ AVATAR_HOST_DOMAIN: ${{ secrets.AVATAR_HOST_DOMAIN }}
43
+ AVATAR_DEFAULT_IMAGE: ${{ secrets.AVATAR_DEFAULT_IMAGE }}
44
44
  BANNER_BASE_URL: ${{ secrets.BANNER_BASE_URL }}
45
45
  BANNER_DEFAULT_URL: ${{ secrets.BANNER_DEFAULT_URL }}
46
46
  LOGFIRE_TOKEN: ${{ secrets.LOGFIRE_TOKEN }}
@@ -4,6 +4,35 @@ Notable changes to **core-framework** (import **`core_framework`**). Format foll
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.4.0] - 2026-05-19
8
+
9
+ ### Changed
10
+
11
+ - **Breaking:** **`avatar`** on profile and related user responses is an object (**`size_128`**, **`size_500`**, **`fallback`**) instead of a single URL string. See **`docs/flows/users/avatar.md`**.
12
+ - **Breaking:** **`[avatar]`** config uses **`avatar_host_domain`** + **`default_avatar_image`** (deploy env **`AVATAR_HOST_DOMAIN`**, **`AVATAR_DEFAULT_IMAGE`**) instead of **`base_url`** / **`default_url`**.
13
+ - **Typed PATCH payloads** — user (preferences, account, profile + admin profile), post, and comment PATCH routes map Pydantic requests to domain `*Update` types at the API boundary; no `model_dump(mode="json")` or dict partial-update payloads through application/domain/repository. See `docs/patch-update-typed-payload.md` and `docs/conventions.md` (*PATCH partial updates*).
14
+ - Self-service profile PATCH invalidates **`USER_DETAIL`** only when **`display_name`** or **`avatar_id`** change (fields present in cached identity). **`profile_visibility`** is coerced to **`ProfileVisibility`** on repository read.
15
+
16
+ ### Added
17
+
18
+ - Optional **`date_of_birth`** on user profiles: calendar date (`YYYY-MM-DD`), not in the future, omit unchanged / **`null`** clears. Exposed on **`GET`** / **`PATCH /users/me/profile`** and on admin user detail plus **`PATCH /admin/users/{user_id}/profile`**. Not returned on other users’ public surfaces (follow lists, post author cards, etc.).
19
+
20
+ ### Database
21
+
22
+ - User domain migration **`user_add_date_of_birth`**: nullable **`date_of_birth`** column on **`user_profiles`**. Run **`uv run cf-alembic`** (or your host migration step) before deploying application code that reads or writes the field.
23
+
24
+ ### Documentation
25
+
26
+ - **`docs/flows/users/avatar.md`**: multi-size **`avatar`** response shape and CDN URL convention.
27
+ - **`docs/flows/media/upload_pipeline.md`**: upload pipeline design (implementation pending).
28
+ - **`docs/flows/users/profile.md`**: DOB on get/update flows and validation errors.
29
+ - **`docs/flows/users/change_history.md`**: profile **`date_of_birth`** changes are not written to change history (audit trigger unchanged).
30
+ - **`docs/patch-update-typed-payload.md`**: typed PATCH decisions and patterns for partial-update endpoints.
31
+
32
+ ### Fixed
33
+
34
+ - **Dockerfile**: include **`README.md`** in the build context so hatchling can resolve the package readme.
35
+
7
36
  ## [1.3.0] - 2026-05-16
8
37
 
9
38
  ### Added
@@ -140,4 +169,5 @@ First **SemVer-stable** release per **`docs/package-api.md`**.
140
169
  [1.1.1]: https://github.com/NepNepFFXIV/core-framework/compare/v1.1.0...v1.1.1
141
170
  [1.2.0]: https://github.com/NepNepFFXIV/core-framework/compare/v1.1.1...v1.2.0
142
171
  [1.3.0]: https://github.com/NepNepFFXIV/core-framework/compare/v1.2.0...v1.3.0
143
- [unreleased]: https://github.com/NepNepFFXIV/core-framework/compare/v1.3.0...HEAD
172
+ [1.4.0]: https://github.com/NepNepFFXIV/core-framework/compare/v1.3.0...v1.4.0
173
+ [unreleased]: https://github.com/NepNepFFXIV/core-framework/compare/v1.4.0...HEAD
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: core-framework
3
- Version: 1.3.0
3
+ Version: 1.4.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
@@ -0,0 +1,27 @@
1
+ """add optional date_of_birth to user_profiles
2
+
3
+ Revision ID: user_add_date_of_birth
4
+ Revises: v1_user_init
5
+ Create Date: 2026-05-18 12:00:00.000000
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ from alembic import op
12
+
13
+ revision: str = "user_add_date_of_birth"
14
+ down_revision: str | Sequence[str] | None = "v1_user_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
+ alter table user_profiles
22
+ add column date_of_birth date;
23
+ """)
24
+
25
+
26
+ def downgrade() -> None:
27
+ pass
@@ -11,8 +11,8 @@ allowed_hosts = [
11
11
  secret_key = "local-dev-secret-key-change-in-production"
12
12
 
13
13
  [avatar]
14
- base_url = "https://avatar.core-framework.com"
15
- default_url = "https://avatar.core-framework.com/default.webp"
14
+ avatar_host_domain = "https://avatar.core-framework.com"
15
+ default_avatar_image = "default.webp"
16
16
 
17
17
  [banner]
18
18
  base_url = "https://banner.core-framework.com"
@@ -7,8 +7,8 @@ allowed_hosts = $ALLOWED_HOSTS
7
7
  secret_key = "$SECRET_KEY"
8
8
 
9
9
  [avatar]
10
- base_url = "$AVATAR_BASE_URL"
11
- default_url = "$AVATAR_DEFAULT_URL"
10
+ avatar_host_domain = "$AVATAR_HOST_DOMAIN"
11
+ default_avatar_image = "$AVATAR_DEFAULT_IMAGE"
12
12
 
13
13
  [banner]
14
14
  base_url = "$BANNER_BASE_URL"
@@ -16,6 +16,7 @@ from core_framework.api.admin.users.schemas import (
16
16
  validate_note_id,
17
17
  )
18
18
  from core_framework.api.dependencies import RequiredUserID
19
+ from core_framework.api.users.mappers import profile_update_from_request
19
20
  from core_framework.api.users.shared.schemas import validate_user_id
20
21
  from core_framework.application.users.admin_service import (
21
22
  add_user_note,
@@ -104,11 +105,10 @@ async def patch_user_profile(
104
105
  request_body: AdminProfileUpdateRequest,
105
106
  ) -> None:
106
107
  validated_target_user_id = validate_user_id(user_id=target_user_id)
107
- validated_update_request = request_body.model_dump(mode="json", exclude_unset=True)
108
108
  await change_user_profile(
109
109
  actor_id=actor_id.root,
110
110
  target_user_id=validated_target_user_id.root,
111
- validated_update_request=validated_update_request,
111
+ update=profile_update_from_request(request=request_body),
112
112
  )
113
113
 
114
114
 
@@ -10,6 +10,7 @@ from core_framework.api.users.shared.schemas import (
10
10
  AvatarMixin,
11
11
  BannerMixin,
12
12
  Bio,
13
+ DateOfBirth,
13
14
  DisplayName,
14
15
  UserID,
15
16
  Username,
@@ -68,17 +69,18 @@ class AdminAccountResponse(BaseModel):
68
69
  user_id: UserID
69
70
  username: str
70
71
  role: UserRole
71
- email: str | None = None
72
+ email: str | None
72
73
  email_verified: bool
73
- deletion_scheduled_for: datetime | None = None
74
+ deletion_scheduled_for: datetime | None
74
75
 
75
76
 
76
77
  class AdminProfileResponse(AvatarMixin, BannerMixin):
77
- display_name: DisplayName | None = None
78
- bio: Bio | None = None
79
- status: UserStatus | None = None
78
+ display_name: DisplayName | None
79
+ bio: Bio | None
80
+ status: UserStatus | None
80
81
  social_links: Annotated[dict[str, str], Field(default_factory=dict)]
81
82
  profile_visibility: ProfileVisibility
83
+ date_of_birth: DateOfBirth | None
82
84
 
83
85
 
84
86
  class UserDetailResponse(BaseModel):
@@ -108,6 +110,7 @@ class AdminProfileUpdateRequest(BasePatchRequest):
108
110
  status: UserStatus | None = None
109
111
  social_links: dict[str, str] | None = None
110
112
  profile_visibility: ProfileVisibility | None = None
113
+ date_of_birth: DateOfBirth | None = None
111
114
 
112
115
 
113
116
  class UserChangeHistoryItem(BaseModel):
@@ -0,0 +1,10 @@
1
+ from core_framework.api.comments.authenticated.schemas import UpdateCommentRequest
2
+ from core_framework.domains.comment import CommentUpdate
3
+
4
+
5
+ def comment_update_from_request(*, request: UpdateCommentRequest) -> CommentUpdate:
6
+ fields_set = frozenset(request.model_fields_set)
7
+ return CommentUpdate(
8
+ fields_set=fields_set,
9
+ content=request.content.root if "content" in fields_set and request.content is not None else None,
10
+ )
@@ -1,6 +1,7 @@
1
1
  from fastapi import APIRouter, BackgroundTasks, Depends, status
2
2
  from ulid import ULID
3
3
 
4
+ from core_framework.api.comments.authenticated.mappers import comment_update_from_request
4
5
  from core_framework.api.comments.authenticated.schemas import (
5
6
  CommentReportRequest,
6
7
  CreateCommentRequest,
@@ -86,11 +87,10 @@ async def patch_comment(
86
87
  user_id: RequiredUserID,
87
88
  request_body: UpdateCommentRequest,
88
89
  ) -> None:
89
- validated_update_request = request_body.model_dump(mode="json", exclude_unset=True)
90
90
  await edit_comment(
91
91
  comment_id=str(comment_id),
92
92
  author_id=user_id.root,
93
- validated_update_request=validated_update_request,
93
+ update=comment_update_from_request(request=request_body),
94
94
  )
95
95
 
96
96
 
@@ -0,0 +1,14 @@
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
+ return PostUpdate(
8
+ fields_set=fields_set,
9
+ content=request.content.root if "content" in fields_set and request.content is not None else None,
10
+ visibility=request.visibility if "visibility" in fields_set else None,
11
+ hashtags=(None if request.hashtags is None else [hashtag.root for hashtag in request.hashtags])
12
+ if "hashtags" in fields_set
13
+ else None,
14
+ )
@@ -2,6 +2,7 @@ from fastapi import APIRouter, BackgroundTasks, Depends, status
2
2
  from ulid import ULID
3
3
 
4
4
  from core_framework.api.dependencies import RequiredUserID, check_not_banned
5
+ from core_framework.api.posts.authenticated.mappers import post_update_from_request
5
6
  from core_framework.api.posts.authenticated.schemas import CreatePostRequest, PostReportRequest, UpdatePostRequest
6
7
  from core_framework.application.posts.aggregation_service import mark_post_stats_dirty
7
8
  from core_framework.application.posts.authenticated_service import (
@@ -34,11 +35,10 @@ async def post_post(user_id: RequiredUserID, request_body: CreatePostRequest) ->
34
35
 
35
36
  @router.patch("/{post_id}", status_code=status.HTTP_204_NO_CONTENT)
36
37
  async def patch_post(post_id: ULID, user_id: RequiredUserID, request_body: UpdatePostRequest) -> None:
37
- validated_update_request = request_body.model_dump(mode="json", exclude_unset=True)
38
38
  await edit_post(
39
39
  post_id=str(post_id),
40
40
  author_id=user_id.root,
41
- validated_update_request=validated_update_request,
41
+ update=post_update_from_request(request=request_body),
42
42
  )
43
43
 
44
44
 
@@ -0,0 +1,21 @@
1
+ from core_framework.api.users.authenticated.schemas import AccountUpdateRequest, PreferencesUpdateRequest
2
+ from core_framework.domains.user import AccountUpdate, PreferencesUpdate
3
+
4
+
5
+ def preferences_update_from_request(*, request: PreferencesUpdateRequest) -> PreferencesUpdate:
6
+ fields_set = frozenset(request.model_fields_set)
7
+ return PreferencesUpdate(
8
+ fields_set=fields_set,
9
+ theme=request.theme if "theme" in fields_set else None,
10
+ language=request.language if "language" in fields_set else None,
11
+ )
12
+
13
+
14
+ def account_update_from_request(*, request: AccountUpdateRequest) -> AccountUpdate:
15
+ fields_set = frozenset(request.model_fields_set)
16
+ return AccountUpdate(
17
+ fields_set=fields_set,
18
+ username=(request.username.root if request.username is not None else None)
19
+ if "username" in fields_set
20
+ else None,
21
+ )
@@ -5,6 +5,10 @@ from fastapi import APIRouter, BackgroundTasks, Depends, Query, Request, status
5
5
  from core_framework.api.comments.public.schemas import CommentResponse
6
6
  from core_framework.api.dependencies import RequiredFirebaseUser, RequiredUserID, check_not_banned
7
7
  from core_framework.api.posts.public.schemas import PostResponse
8
+ from core_framework.api.users.authenticated.mappers import (
9
+ account_update_from_request,
10
+ preferences_update_from_request,
11
+ )
8
12
  from core_framework.api.users.authenticated.schemas import (
9
13
  AccountResponse,
10
14
  AccountUpdateRequest,
@@ -18,6 +22,7 @@ from core_framework.api.users.authenticated.schemas import (
18
22
  ProfileUpdateRequest,
19
23
  UserReportRequest,
20
24
  )
25
+ from core_framework.api.users.mappers import profile_update_from_request
21
26
  from core_framework.api.users.shared.schemas import UsernameSuggestQuery, UserReference, validate_user_id
22
27
  from core_framework.application.comments.public_service import retrieve_my_comments
23
28
  from core_framework.application.posts.public_service import retrieve_my_posts
@@ -209,8 +214,10 @@ async def get_my_preferences(user_id: RequiredUserID) -> Any:
209
214
  dependencies=[Depends(check_not_banned)],
210
215
  )
211
216
  async def patch_my_preferences(user_id: RequiredUserID, update_request: PreferencesUpdateRequest) -> Any:
212
- validated_update_request = update_request.model_dump(mode="json", exclude_unset=True)
213
- return await change_my_preferences(user_id=user_id.root, validated_update_request=validated_update_request)
217
+ return await change_my_preferences(
218
+ user_id=user_id.root,
219
+ update=preferences_update_from_request(request=update_request),
220
+ )
214
221
 
215
222
 
216
223
  @router.get(
@@ -228,8 +235,10 @@ async def get_my_profile(user_id: RequiredUserID) -> Any:
228
235
  dependencies=[Depends(check_not_banned)],
229
236
  )
230
237
  async def patch_my_profile(user_id: RequiredUserID, update_request: ProfileUpdateRequest) -> Any:
231
- validated_update_request = update_request.model_dump(mode="json", exclude_unset=True)
232
- return await change_my_profile(user_id=user_id.root, validated_update_request=validated_update_request)
238
+ return await change_my_profile(
239
+ user_id=user_id.root,
240
+ update=profile_update_from_request(request=update_request),
241
+ )
233
242
 
234
243
 
235
244
  @router.get(
@@ -254,8 +263,10 @@ async def patch_my_account(
254
263
  firebase_user: RequiredFirebaseUser,
255
264
  update_request: AccountUpdateRequest,
256
265
  ) -> Any:
257
- validated_update_request = update_request.model_dump(mode="json", exclude_unset=True)
258
- await change_my_account(user_id=firebase_user.user_id.root, validated_update_request=validated_update_request)
266
+ await change_my_account(
267
+ user_id=firebase_user.user_id.root,
268
+ update=account_update_from_request(request=update_request),
269
+ )
259
270
  return await retrieve_my_account(
260
271
  user_id=firebase_user.user_id.root,
261
272
  email=firebase_user.email,
@@ -9,6 +9,7 @@ from core_framework.api.users.shared.schemas import (
9
9
  AvatarMixin,
10
10
  BannerMixin,
11
11
  Bio,
12
+ DateOfBirth,
12
13
  DisplayName,
13
14
  UserID,
14
15
  Username,
@@ -37,6 +38,7 @@ class ProfileResponse(AvatarMixin, BannerMixin):
37
38
  status: UserStatus | None
38
39
  social_links: dict[str, str]
39
40
  profile_visibility: ProfileVisibility
41
+ date_of_birth: DateOfBirth | None
40
42
 
41
43
 
42
44
  class AccountResponse(BaseModel):
@@ -58,6 +60,7 @@ class ProfileUpdateRequest(BasePatchRequest):
58
60
  status: UserStatus | None = None
59
61
  social_links: dict[str, str] | None = None
60
62
  profile_visibility: ProfileVisibility | None = None
63
+ date_of_birth: DateOfBirth | None = None
61
64
 
62
65
 
63
66
  class UserReportRequest(BaseModel):
@@ -0,0 +1,77 @@
1
+ from datetime import date
2
+
3
+ from ulid import ULID
4
+
5
+ from core_framework.api.admin.users.schemas import AdminProfileUpdateRequest
6
+ from core_framework.api.users.authenticated.schemas import ProfileUpdateRequest
7
+ from core_framework.domains.user import ProfileUpdate
8
+
9
+ ProfilePatchRequest = ProfileUpdateRequest | AdminProfileUpdateRequest
10
+
11
+
12
+ def profile_update_from_request(*, request: ProfilePatchRequest) -> ProfileUpdate:
13
+ fields_set = frozenset(request.model_fields_set)
14
+ return ProfileUpdate(
15
+ fields_set=fields_set,
16
+ display_name=_display_name_from_request(request=request, fields_set=fields_set),
17
+ avatar_id=_ulid_string_from_request(
18
+ value=request.avatar_id,
19
+ field="avatar_id",
20
+ fields_set=fields_set,
21
+ ),
22
+ banner_id=_ulid_string_from_request(
23
+ value=request.banner_id,
24
+ field="banner_id",
25
+ fields_set=fields_set,
26
+ ),
27
+ bio=_bio_from_request(request=request, fields_set=fields_set),
28
+ status=_status_from_request(request=request, fields_set=fields_set),
29
+ social_links=request.social_links if "social_links" in fields_set else None,
30
+ profile_visibility=request.profile_visibility if "profile_visibility" in fields_set else None,
31
+ date_of_birth=_date_of_birth_from_request(request=request, fields_set=fields_set),
32
+ )
33
+
34
+
35
+ def _display_name_from_request(*, request: ProfilePatchRequest, fields_set: frozenset[str]) -> str | None:
36
+ if "display_name" not in fields_set:
37
+ return None
38
+ if request.display_name is None:
39
+ return None
40
+ return request.display_name.root
41
+
42
+
43
+ def _bio_from_request(*, request: ProfilePatchRequest, fields_set: frozenset[str]) -> str | None:
44
+ if "bio" not in fields_set:
45
+ return None
46
+ if request.bio is None:
47
+ return None
48
+ return request.bio.root
49
+
50
+
51
+ def _status_from_request(*, request: ProfilePatchRequest, fields_set: frozenset[str]) -> str | None:
52
+ if "status" not in fields_set:
53
+ return None
54
+ if request.status is None:
55
+ return None
56
+ return request.status.root
57
+
58
+
59
+ def _date_of_birth_from_request(*, request: ProfilePatchRequest, fields_set: frozenset[str]) -> date | None:
60
+ if "date_of_birth" not in fields_set:
61
+ return None
62
+ if request.date_of_birth is None:
63
+ return None
64
+ return request.date_of_birth.root
65
+
66
+
67
+ def _ulid_string_from_request(
68
+ *,
69
+ value: ULID | None,
70
+ field: str,
71
+ fields_set: frozenset[str],
72
+ ) -> str | None:
73
+ if field not in fields_set:
74
+ return None
75
+ if value is None:
76
+ return None
77
+ return str(value)
@@ -1,3 +1,4 @@
1
+ from datetime import date
1
2
  from typing import Annotated
2
3
 
3
4
  from fastapi.exceptions import RequestValidationError
@@ -75,6 +76,12 @@ def _get_user_media_urls() -> dict[str, str]:
75
76
  raise RuntimeError(msg)
76
77
 
77
78
 
79
+ class AvatarUrls(BaseModel):
80
+ size_128: HttpUrl
81
+ size_500: HttpUrl
82
+ fallback: HttpUrl
83
+
84
+
78
85
  class UserID(RootModel[str]):
79
86
  model_config = ConfigDict(frozen=True)
80
87
  root: Annotated[
@@ -140,14 +147,28 @@ class DisplayName(RootModel[str]):
140
147
  ]
141
148
 
142
149
 
150
+ class DateOfBirth(RootModel[date]):
151
+ model_config = ConfigDict(frozen=True)
152
+ root: Annotated[
153
+ date,
154
+ Field(description="User date of birth (calendar date)"),
155
+ ]
156
+
157
+ @field_validator("root", mode="after")
158
+ @classmethod
159
+ def not_future(cls, v: date) -> date:
160
+ if v > date.today():
161
+ raise ValueError("date_of_birth cannot be in the future")
162
+ return v
163
+
164
+
143
165
  class AvatarMixin(BaseModel):
144
166
  avatar_id: Annotated[str | None, Field(exclude=True)]
145
167
 
146
168
  @computed_field
147
169
  @property
148
- def avatar(self) -> HttpUrl:
149
- url_string = _construct_avatar_url(avatar_id=self.avatar_id)
150
- return _http_url_adapter.validate_python(url_string)
170
+ def avatar(self) -> AvatarUrls:
171
+ return _construct_avatar_urls(avatar_id=self.avatar_id)
151
172
 
152
173
 
153
174
  class BannerMixin(BaseModel):
@@ -179,11 +200,19 @@ def validate_optional_user_id(*, user_id: str | None) -> UserID | None:
179
200
  return validate_user_id(user_id=user_id)
180
201
 
181
202
 
182
- def _construct_avatar_url(*, avatar_id: str | None) -> str:
203
+ def _construct_avatar_urls(*, avatar_id: str | None) -> AvatarUrls:
183
204
  media_urls = _get_user_media_urls()
205
+ fallback = media_urls["avatar_default_url"]
206
+ base = media_urls["avatar_base_url"]
207
+ ext = media_urls["avatar_extension"]
184
208
  if avatar_id is None:
185
- return media_urls["avatar_default_url"]
186
- return f"{media_urls['avatar_base_url']}/{avatar_id}.{media_urls['avatar_extension']}"
209
+ fb = _http_url_adapter.validate_python(fallback)
210
+ return AvatarUrls(size_128=fb, size_500=fb, fallback=fb)
211
+ return AvatarUrls(
212
+ size_128=_http_url_adapter.validate_python(f"{base}/128/{avatar_id}.{ext}"),
213
+ size_500=_http_url_adapter.validate_python(f"{base}/500/{avatar_id}.{ext}"),
214
+ fallback=_http_url_adapter.validate_python(fallback),
215
+ )
187
216
 
188
217
 
189
218
  def _construct_banner_url(*, banner_id: str | None) -> str:
@@ -1,11 +1,9 @@
1
- from typing import Any
2
-
3
1
  import core_framework.application.notifications.notification_service as notification_service
4
2
  import core_framework.domains.comment.dependencies as comment_deps
5
3
  import core_framework.domains.moderation.dependencies as moderation_deps
6
4
  from core_framework.application.comments import aggregation_service as comment_aggregation_service
7
5
  from core_framework.application.posts import aggregation_service as post_aggregation_service
8
- from core_framework.domains.comment import CommentStatsImpact, CommentSubjectType
6
+ from core_framework.domains.comment import CommentStatsImpact, CommentSubjectType, CommentUpdate
9
7
  from core_framework.domains.moderation import ReportCategory
10
8
 
11
9
 
@@ -63,13 +61,9 @@ async def edit_comment(
63
61
  *,
64
62
  comment_id: str,
65
63
  author_id: str,
66
- validated_update_request: dict[str, Any],
64
+ update: CommentUpdate,
67
65
  ) -> None:
68
- await comment_deps.comment_service.edit_comment(
69
- comment_id=comment_id,
70
- author_id=author_id,
71
- validated_update_request=validated_update_request,
72
- )
66
+ await comment_deps.comment_service.edit_comment(comment_id=comment_id, author_id=author_id, update=update)
73
67
 
74
68
 
75
69
  async def like_comment(*, comment_id: str, user_id: str) -> bool:
@@ -1,10 +1,8 @@
1
- from typing import Any
2
-
3
1
  import core_framework.application.notifications.notification_service as notification_service
4
2
  import core_framework.domains.moderation.dependencies as moderation_deps
5
3
  import core_framework.domains.post.dependencies as post_deps
6
4
  from core_framework.domains.moderation import ReportCategory
7
- from core_framework.domains.post import PostStatus, PostVisibility
5
+ from core_framework.domains.post import PostStatus, PostUpdate, PostVisibility
8
6
 
9
7
 
10
8
  async def create_post(
@@ -33,13 +31,9 @@ async def edit_post(
33
31
  *,
34
32
  post_id: str,
35
33
  author_id: str,
36
- validated_update_request: dict[str, Any],
34
+ update: PostUpdate,
37
35
  ) -> None:
38
- await post_deps.post_service.edit_post(
39
- post_id=post_id,
40
- author_id=author_id,
41
- validated_update_request=validated_update_request,
42
- )
36
+ await post_deps.post_service.edit_post(post_id=post_id, author_id=author_id, update=update)
43
37
 
44
38
 
45
39
  async def delete_post(*, post_id: str, author_id: str) -> None: