core-framework 1.2.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.
- {core_framework-1.2.0 → core_framework-1.4.0}/.cursor/rules/constants-final.mdc +1 -1
- {core_framework-1.2.0 → core_framework-1.4.0}/.cursor/rules/database-triggers.md +1 -24
- {core_framework-1.2.0 → core_framework-1.4.0}/.cursor/rules/domain-input-guards.mdc +3 -2
- {core_framework-1.2.0 → core_framework-1.4.0}/.dockerignore +2 -3
- {core_framework-1.2.0 → core_framework-1.4.0}/.github/workflows/_deploy.yml +2 -2
- core_framework-1.4.0/.github/workflows/publish-pypi.yml +34 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/CHANGELOG.md +47 -1
- core_framework-1.4.0/PKG-INFO +115 -0
- core_framework-1.4.0/README.md +81 -0
- core_framework-1.4.0/alembic/user/alembic/versions/user_add_date_of_birth_to_profiles.py +27 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/config.toml +2 -2
- {core_framework-1.2.0 → core_framework-1.4.0}/config.toml.template +2 -2
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/admin/users/router.py +2 -2
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/admin/users/schemas.py +8 -5
- core_framework-1.4.0/core_framework/api/comments/authenticated/mappers.py +10 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/comments/authenticated/router.py +2 -2
- core_framework-1.4.0/core_framework/api/posts/authenticated/mappers.py +14 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/posts/authenticated/router.py +2 -2
- core_framework-1.4.0/core_framework/api/users/authenticated/mappers.py +21 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/users/authenticated/router.py +17 -6
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/users/authenticated/schemas.py +3 -0
- core_framework-1.4.0/core_framework/api/users/mappers.py +77 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/users/shared/schemas.py +35 -6
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/comments/authenticated_service.py +3 -9
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/posts/authenticated_service.py +3 -9
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/users/admin_service.py +17 -12
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/users/authenticated_service.py +19 -23
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/core/firebase.py +8 -1
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/core/settings.py +16 -3
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/comment/__init__.py +2 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/comment/models.py +30 -1
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/comment/repository.py +4 -5
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/comment/service.py +7 -8
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/post/__init__.py +2 -1
- core_framework-1.4.0/core_framework/domains/post/models.py +98 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/post/repository.py +5 -12
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/post/service.py +7 -12
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/user/__init__.py +6 -0
- core_framework-1.4.0/core_framework/domains/user/constants.py +11 -0
- core_framework-1.4.0/core_framework/domains/user/models.py +269 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/user/repository.py +23 -25
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/user/service.py +14 -38
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/utils.py +0 -13
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/main.py +2 -2
- core_framework-1.4.0/docker-compose.dev.yaml +24 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/dockerfile +1 -1
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/conventions.md +8 -1
- core_framework-1.4.0/docs/flows/media/upload_pipeline.md +454 -0
- core_framework-1.4.0/docs/flows/users/avatar.md +91 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/users/change_history.md +2 -1
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/users/profile.md +10 -11
- core_framework-1.4.0/docs/patch-update-typed-payload.md +186 -0
- core_framework-1.4.0/docs/todo.md +11 -0
- core_framework-1.4.0/firebase_config.example.json +13 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/pyproject.toml +1 -1
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/admin/users/router_test.py +42 -2
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/users/authenticated/router_test.py +90 -12
- {core_framework-1.2.0 → core_framework-1.4.0}/uv.lock +1 -1
- core_framework-1.2.0/.logfire/.gitignore +0 -1
- core_framework-1.2.0/.logfire/logfire_credentials.json +0 -6
- core_framework-1.2.0/PKG-INFO +0 -131
- core_framework-1.2.0/README.md +0 -97
- core_framework-1.2.0/core_framework/domains/post/models.py +0 -61
- core_framework-1.2.0/core_framework/domains/user/constants.py +0 -41
- core_framework-1.2.0/core_framework/domains/user/models.py +0 -153
- {core_framework-1.2.0 → core_framework-1.4.0}/.cursor/rules/api-layer.mdc +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.cursor/rules/api-reference-docs.mdc +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.cursor/rules/api-security.mdc +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.cursor/rules/api-validation.mdc +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.cursor/rules/application-layer.mdc +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.cursor/rules/domain-caller-context.mdc +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.cursor/rules/domain-imports.mdc +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.cursor/rules/domain-repository-exceptions.mdc +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.cursor/rules/flow-documentation.mdc +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.cursor/rules/implementation-workflow.mdc +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.cursor/rules/integration-test-strategy.mdc +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.cursor/rules/layer-boundaries.mdc +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.cursor/rules/no-code-in-docs.mdc +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.cursor/rules/no-docstrings.mdc +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.cursor/rules/postgres-config-conventions.mdc +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.cursor/rules/repository-read-consistency.mdc +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.cursor/rules/strong-read-opt-in.mdc +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.cursor/rules/tech-stack.mdc +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.cursor/skills/add-config/SKILL.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.cursor/skills/add-domain/SKILL.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.cursor/skills/code-review/SKILL.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.cursor/skills/recommend-features/SKILL.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.github/workflows/dev-ci-cd.yaml +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.github/workflows/manual-deployment.yaml +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.github/workflows/test.yaml +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.gitignore +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.pre-commit-config.yaml +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/.python-version +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/LICENSE +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/comment/alembic/README +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/comment/alembic/env.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/comment/alembic/script.py.mako +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/comment/alembic/versions/v1_comment_init_baseline.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/comment/alembic.ini +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/extension/alembic/README +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/extension/alembic/env.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/extension/alembic/script.py.mako +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/extension/alembic/versions/v1_ext_init_baseline.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/extension/alembic.ini +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/moderation/alembic/README +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/moderation/alembic/env.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/moderation/alembic/script.py.mako +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/moderation/alembic/versions/v1_mod_init_baseline.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/moderation/alembic.ini +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/notification/alembic/README +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/notification/alembic/env.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/notification/alembic/script.py.mako +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/notification/alembic/versions/v1_notif_init_baseline.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/notification/alembic.ini +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/post/alembic/README +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/post/alembic/env.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/post/alembic/script.py.mako +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/post/alembic/versions/v1_post_init_baseline.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/post/alembic.ini +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/user/alembic/README +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/user/alembic/env.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/user/alembic/script.py.mako +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/user/alembic/versions/v1_user_init_baseline.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/alembic/user/alembic.ini +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/admin/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/admin/comments/router.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/admin/comments/schemas.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/admin/moderation/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/admin/moderation/router.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/admin/moderation/schemas.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/admin/posts/router.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/admin/posts/schemas.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/admin/router.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/admin/users/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/auth/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/auth/router.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/auth/schemas.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/comments/authenticated/schemas.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/comments/public/router.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/comments/public/schemas.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/comments/router.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/comments/schemas.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/constants.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/dependencies.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/events/router.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/events/schemas.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/notifications/authenticated/router.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/notifications/authenticated/schemas.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/notifications/router.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/posts/authenticated/schemas.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/posts/public/router.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/posts/public/schemas.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/posts/router.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/posts/schemas.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/router.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/schemas.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/system/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/system/router.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/users/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/users/authenticated/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/users/public/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/users/public/router.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/users/public/schemas.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/users/router.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/auth/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/auth/access_service.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/auth/auth_service.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/auth/models.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/bootstrap.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/comments/admin_service.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/comments/aggregation_service.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/comments/public_service.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/events/README.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/events/event_service.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/events/event_token.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/events/models.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/moderation/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/moderation/appeal_service.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/moderation/moderator_service.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/moderation/report_service.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/moderation/scheduled_service.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/moderation/user_service.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/notifications/README.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/notifications/enums.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/notifications/inbox_service.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/notifications/mute_service.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/notifications/notification_service.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/posts/admin_service.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/posts/aggregation_service.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/posts/public_service.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/shared/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/shared/enums.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/shared/exceptions.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/shared/user_agent.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/users/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/users/aggregation_service.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/users/public_service.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/application/users/scheduled_service.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/asgi.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/bundled_alembic.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/constants.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/core/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/core/cache.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/core/context.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/core/database.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/core/exception_handlers/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/core/exception_handlers/comment.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/core/exception_handlers/common.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/core/exception_handlers/moderation.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/core/exception_handlers/notification.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/core/exception_handlers/post.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/core/exception_handlers/setup.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/core/exception_handlers/user.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/core/http_client.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/core/logging.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/core/middleware.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/core/observability.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/core/pagination.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/core/redis.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/core/runtime.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/comment/README.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/comment/constants.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/comment/dependencies.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/comment/enums.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/comment/exceptions.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/exceptions.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/moderation/README.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/moderation/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/moderation/dependencies.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/moderation/enums.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/moderation/exceptions.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/moderation/models.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/moderation/repository.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/moderation/service.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/notification/README.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/notification/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/notification/dependencies.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/notification/enums.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/notification/exceptions.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/notification/models.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/notification/repository.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/notification/service.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/post/README.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/post/constants.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/post/dependencies.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/post/enums.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/post/exceptions.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/user/README.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/user/dependencies.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/user/enums.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/user/exceptions.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/domains/user/utils.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/migrate_cli.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/testing/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/testing/arq.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/testing/auth.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/testing/config.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/testing/containers.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/testing/firebase.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/testing/hookspecs.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/testing/httpx_test_client.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/testing/migrations.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/testing/plugin.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/worker/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/worker/main.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/worker/schedules/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/worker/schedules/schedule_aggregate_comment_stats.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/worker/schedules/schedule_aggregate_post_stats.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/worker/schedules/schedule_aggregate_user_stats.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/worker/schedules/schedule_expired_account_deletions.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/worker/schedules/schedule_expired_mute_lifts.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/worker/tasks/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/worker/tasks/process_account_deletion.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/worker/tasks/process_aggregate_comment_stats.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/worker/tasks/process_aggregate_post_stats.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/worker/tasks/process_aggregate_user_stats.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/worker/tasks/process_mute_lift.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/core_framework/worker/worker_context.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docker-compose.yml +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/api.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/architecture-decisions.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/architecture.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/core-framework-migration.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/database-triggers.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/event-outbox-design.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/auth/access_control.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/auth/registration.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/comments/admin_comments.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/comments/comment_report.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/comments/comment_stats_aggregation.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/comments/create_comment.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/comments/delete_comment.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/comments/edit_comment.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/comments/retrieve_comments.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/events/events.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/mentions/mentions_in_content.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/moderation/appeals.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/moderation/internal_notes.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/moderation/moderator_actions.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/moderation/reports.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/moderation/restrictions.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/notifications/notification_inbox.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/posts/admin_posts.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/posts/author_context.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/posts/hashtag_discovery.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/posts/post_like.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/posts/post_stats_aggregation.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/users/account.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/users/account_deletion.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/users/blocks.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/users/check_username_exists.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/users/follow.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/users/my_posts_and_comments.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/users/preferences.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/flows/users/user_removal.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/follow-system-design.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/package-api.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/docs/testing-plugin-design.md +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/makefile +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/conftest.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/_http_helpers.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/admin/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/admin/comments/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/admin/comments/router_test.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/admin/moderation/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/admin/moderation/router_test.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/admin/posts/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/admin/posts/router_test.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/admin/users/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/auth/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/auth/router_test.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/comments/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/comments/authenticated/comment_writes_integration_test.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/comments/public/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/comments/public/router_test.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/events/router_test.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/notifications/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/notifications/router_test.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/posts/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/posts/authenticated/post_writes_integration_test.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/posts/comment_count_aggregation_test.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/posts/followers_visibility_test.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/posts/post_stats_dirty_marking_test.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/posts/public/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/posts/public/router_test.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/system/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/system/router_test.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/users/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/users/authenticated/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/users/public/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/api/users/public/router_test.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/worker/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/worker/account_deletion_test.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/worker/aggregate_comment_stats_test.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/worker/aggregate_post_stats_test.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/worker/aggregate_user_stats_test.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/worker/conftest.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/worker/mute_lift_test.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/integration/worker/utils_test.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/unit/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/unit/application/comments/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/unit/application/events/event_service_test.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/unit/application/notifications/inbox_service_test.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/unit/core/bundled_alembic_test.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/unit/core/migrate_cli_test.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/unit/core/pagination_test.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/unit/domains/__init__.py +0 -0
- {core_framework-1.2.0 → core_framework-1.4.0}/tests/unit/domains/comment/__init__.py +0 -0
- {core_framework-1.2.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. **`
|
|
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`**.
|
|
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.
|
|
@@ -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
|
-
|
|
43
|
-
|
|
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 }}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
|
|
12
|
+
steps:
|
|
13
|
+
- name: Checkout code
|
|
14
|
+
uses: actions/checkout@v6
|
|
15
|
+
|
|
16
|
+
- name: Install uv
|
|
17
|
+
uses: astral-sh/setup-uv@v7
|
|
18
|
+
with:
|
|
19
|
+
version: "0.7.9"
|
|
20
|
+
enable-cache: true
|
|
21
|
+
cache-dependency-glob: "uv.lock"
|
|
22
|
+
|
|
23
|
+
- name: Set up Python
|
|
24
|
+
uses: actions/setup-python@v6
|
|
25
|
+
with:
|
|
26
|
+
python-version-file: ".python-version"
|
|
27
|
+
|
|
28
|
+
- name: Build
|
|
29
|
+
run: uv build
|
|
30
|
+
|
|
31
|
+
- name: Publish
|
|
32
|
+
run: uv publish
|
|
33
|
+
env:
|
|
34
|
+
UV_PUBLISH_TOKEN: ${{ secrets.UV_PUBLISH_TOKEN }}
|
|
@@ -4,6 +4,50 @@ 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
|
+
|
|
36
|
+
## [1.3.0] - 2026-05-16
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- **`docker-compose.dev.yaml`** for local Postgres and Redis (aligned with repo **`config.toml`**).
|
|
41
|
+
- **`firebase_config.example.json`** and **`FileNotFoundError`** with next-step text when **`firebase_config.json`** is missing in **`init_firebase`**.
|
|
42
|
+
|
|
43
|
+
### Documentation
|
|
44
|
+
|
|
45
|
+
- **`README.md`**: install (**`uv`**), local development, Firebase file copy, host / pytest integration examples.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- Example Firebase JSON uses no PEM markers so **`detect-private-key`** pre-commit hooks do not false-positive.
|
|
50
|
+
|
|
7
51
|
## [1.2.0] - 2026-05-02
|
|
8
52
|
|
|
9
53
|
### Changed
|
|
@@ -124,4 +168,6 @@ First **SemVer-stable** release per **`docs/package-api.md`**.
|
|
|
124
168
|
[1.1.0]: https://github.com/NepNepFFXIV/core-framework/compare/v1.0.0...v1.1.0
|
|
125
169
|
[1.1.1]: https://github.com/NepNepFFXIV/core-framework/compare/v1.1.0...v1.1.1
|
|
126
170
|
[1.2.0]: https://github.com/NepNepFFXIV/core-framework/compare/v1.1.1...v1.2.0
|
|
127
|
-
[
|
|
171
|
+
[1.3.0]: https://github.com/NepNepFFXIV/core-framework/compare/v1.2.0...v1.3.0
|
|
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
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: core-framework
|
|
3
|
+
Version: 1.4.0
|
|
4
|
+
Summary: Core framework package (import as core_framework)
|
|
5
|
+
Project-URL: Homepage, https://github.com/NepNepFFXIV/core-framework
|
|
6
|
+
Project-URL: Repository, https://github.com/NepNepFFXIV/core-framework
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Python: >=3.14
|
|
10
|
+
Requires-Dist: alembic>=1.18.4
|
|
11
|
+
Requires-Dist: arq>=0.28.0
|
|
12
|
+
Requires-Dist: async-lru>=2.1.0
|
|
13
|
+
Requires-Dist: asyncer>=0.0.14
|
|
14
|
+
Requires-Dist: asyncpg>=0.31.0
|
|
15
|
+
Requires-Dist: brotli-asgi>=1.6.0
|
|
16
|
+
Requires-Dist: cashews[redis]>=7.5.0
|
|
17
|
+
Requires-Dist: device-detector>=6.2.0
|
|
18
|
+
Requires-Dist: fastapi[standard]>=0.136.1
|
|
19
|
+
Requires-Dist: firebase-admin>=7.4.0
|
|
20
|
+
Requires-Dist: httpx[http2]>=0.28.1
|
|
21
|
+
Requires-Dist: itsdangerous>=2.2.0
|
|
22
|
+
Requires-Dist: logfire[asyncpg,fastapi,httpx,redis]>=4.32.1
|
|
23
|
+
Requires-Dist: mashumaro[orjson]>=3.20
|
|
24
|
+
Requires-Dist: orjson>=3.11.7
|
|
25
|
+
Requires-Dist: python-ulid>=3.1.0
|
|
26
|
+
Requires-Dist: structlog>=25.5.0
|
|
27
|
+
Requires-Dist: tenacity>=9.1.4
|
|
28
|
+
Provides-Extra: testing
|
|
29
|
+
Requires-Dist: asgi-lifespan>=2.1.0; extra == 'testing'
|
|
30
|
+
Requires-Dist: pytest-xdist>=3.6.0; extra == 'testing'
|
|
31
|
+
Requires-Dist: pytest>=9.0.3; extra == 'testing'
|
|
32
|
+
Requires-Dist: testcontainers[postgres,redis]>=4.13.2; extra == 'testing'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# Core framework
|
|
36
|
+
|
|
37
|
+
Install from PyPI as **`core-framework`**, import **`core_framework`**. MIT — [LICENSE](LICENSE). [Repository](https://github.com/NepNepFFXIV/core-framework), [PyPI](https://pypi.org/project/core-framework/).
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
uv add core-framework
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Local development
|
|
46
|
+
|
|
47
|
+
Use **Python 3.14**. Install **uv** and **Docker**.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
docker compose -f docker-compose.dev.yaml up -d
|
|
51
|
+
uv sync --locked --all-extras --dev
|
|
52
|
+
uv run cf-alembic
|
|
53
|
+
make run
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Firebase:** copy **`firebase_config.example.json`** to **`firebase_config.json`** at the repo root and replace with your Firebase project’s **service account** JSON from the console.
|
|
57
|
+
|
|
58
|
+
For **`make test`**, use the Firebase CLI (Auth emulator). Stop DB/Redis: **`docker compose -f docker-compose.dev.yaml down`**.
|
|
59
|
+
|
|
60
|
+
## Hosts
|
|
61
|
+
|
|
62
|
+
In your host app, load **`Settings`** or a **`Settings`** subclass from **`core_framework.core.settings`**, pass it to **`init_app`** from **`core_framework.main`**, then wire host-specific dependencies, exception handlers, and routers.
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from core_framework.main import init_app
|
|
66
|
+
from fastapi import FastAPI
|
|
67
|
+
|
|
68
|
+
from myapp.settings import HostSettings, load_default_settings
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def build_app(settings: HostSettings | None = None) -> FastAPI:
|
|
72
|
+
resolved = settings if settings is not None else load_default_settings()
|
|
73
|
+
app = init_app(resolved)
|
|
74
|
+
|
|
75
|
+
from myapp.bootstrap import configure_dependencies
|
|
76
|
+
from myapp.exception_handlers import setup_exception_handlers
|
|
77
|
+
|
|
78
|
+
configure_dependencies(runtime=app.state.core_runtime)
|
|
79
|
+
setup_exception_handlers(app)
|
|
80
|
+
|
|
81
|
+
from myapp.api.router import router
|
|
82
|
+
|
|
83
|
+
app.include_router(router)
|
|
84
|
+
return app
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
From the host repository root, run **`uv run cf-alembic`**. See [core-framework-migration](docs/core-framework-migration.md).
|
|
88
|
+
|
|
89
|
+
## Host pytest
|
|
90
|
+
|
|
91
|
+
In your host repository, add the **`core-framework[testing]`** extra to dev or test dependencies. In **`tests/conftest.py`**, register **`pytest_core_framework_config`** (returns **`TestConfig`**) and a session-scoped **`anyio_backend`** fixture that returns **`"asyncio"`**.
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
import pytest
|
|
95
|
+
|
|
96
|
+
from core_framework.testing import TestConfig
|
|
97
|
+
|
|
98
|
+
from myapp.settings import load_default_settings
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def pytest_core_framework_config() -> TestConfig:
|
|
102
|
+
from myapp.main import build_app
|
|
103
|
+
|
|
104
|
+
return TestConfig(
|
|
105
|
+
settings_loader=load_default_settings,
|
|
106
|
+
app_factory=build_app,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@pytest.fixture(scope="session")
|
|
111
|
+
def anyio_backend() -> str:
|
|
112
|
+
return "asyncio"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
See [Package API — Testing](docs/package-api.md#testing-pytest-plugin) and [testing plugin design](docs/testing-plugin-design.md).
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Core framework
|
|
2
|
+
|
|
3
|
+
Install from PyPI as **`core-framework`**, import **`core_framework`**. MIT — [LICENSE](LICENSE). [Repository](https://github.com/NepNepFFXIV/core-framework), [PyPI](https://pypi.org/project/core-framework/).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv add core-framework
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Local development
|
|
12
|
+
|
|
13
|
+
Use **Python 3.14**. Install **uv** and **Docker**.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
docker compose -f docker-compose.dev.yaml up -d
|
|
17
|
+
uv sync --locked --all-extras --dev
|
|
18
|
+
uv run cf-alembic
|
|
19
|
+
make run
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Firebase:** copy **`firebase_config.example.json`** to **`firebase_config.json`** at the repo root and replace with your Firebase project’s **service account** JSON from the console.
|
|
23
|
+
|
|
24
|
+
For **`make test`**, use the Firebase CLI (Auth emulator). Stop DB/Redis: **`docker compose -f docker-compose.dev.yaml down`**.
|
|
25
|
+
|
|
26
|
+
## Hosts
|
|
27
|
+
|
|
28
|
+
In your host app, load **`Settings`** or a **`Settings`** subclass from **`core_framework.core.settings`**, pass it to **`init_app`** from **`core_framework.main`**, then wire host-specific dependencies, exception handlers, and routers.
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from core_framework.main import init_app
|
|
32
|
+
from fastapi import FastAPI
|
|
33
|
+
|
|
34
|
+
from myapp.settings import HostSettings, load_default_settings
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def build_app(settings: HostSettings | None = None) -> FastAPI:
|
|
38
|
+
resolved = settings if settings is not None else load_default_settings()
|
|
39
|
+
app = init_app(resolved)
|
|
40
|
+
|
|
41
|
+
from myapp.bootstrap import configure_dependencies
|
|
42
|
+
from myapp.exception_handlers import setup_exception_handlers
|
|
43
|
+
|
|
44
|
+
configure_dependencies(runtime=app.state.core_runtime)
|
|
45
|
+
setup_exception_handlers(app)
|
|
46
|
+
|
|
47
|
+
from myapp.api.router import router
|
|
48
|
+
|
|
49
|
+
app.include_router(router)
|
|
50
|
+
return app
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
From the host repository root, run **`uv run cf-alembic`**. See [core-framework-migration](docs/core-framework-migration.md).
|
|
54
|
+
|
|
55
|
+
## Host pytest
|
|
56
|
+
|
|
57
|
+
In your host repository, add the **`core-framework[testing]`** extra to dev or test dependencies. In **`tests/conftest.py`**, register **`pytest_core_framework_config`** (returns **`TestConfig`**) and a session-scoped **`anyio_backend`** fixture that returns **`"asyncio"`**.
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
import pytest
|
|
61
|
+
|
|
62
|
+
from core_framework.testing import TestConfig
|
|
63
|
+
|
|
64
|
+
from myapp.settings import load_default_settings
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def pytest_core_framework_config() -> TestConfig:
|
|
68
|
+
from myapp.main import build_app
|
|
69
|
+
|
|
70
|
+
return TestConfig(
|
|
71
|
+
settings_loader=load_default_settings,
|
|
72
|
+
app_factory=build_app,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@pytest.fixture(scope="session")
|
|
77
|
+
def anyio_backend() -> str:
|
|
78
|
+
return "asyncio"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
See [Package API — Testing](docs/package-api.md#testing-pytest-plugin) and [testing plugin design](docs/testing-plugin-design.md).
|
|
@@ -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
|
-
|
|
15
|
-
|
|
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
|
-
|
|
11
|
-
|
|
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
|
-
|
|
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
|
|
72
|
+
email: str | None
|
|
72
73
|
email_verified: bool
|
|
73
|
-
deletion_scheduled_for: datetime | None
|
|
74
|
+
deletion_scheduled_for: datetime | None
|
|
74
75
|
|
|
75
76
|
|
|
76
77
|
class AdminProfileResponse(AvatarMixin, BannerMixin):
|
|
77
|
-
display_name: DisplayName | None
|
|
78
|
-
bio: Bio | None
|
|
79
|
-
status: UserStatus | 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
|
+
)
|
{core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/comments/authenticated/router.py
RENAMED
|
@@ -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
|
-
|
|
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
|
+
)
|
{core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/posts/authenticated/router.py
RENAMED
|
@@ -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
|
-
|
|
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
|
+
)
|
{core_framework-1.2.0 → core_framework-1.4.0}/core_framework/api/users/authenticated/router.py
RENAMED
|
@@ -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
|
-
|
|
213
|
-
|
|
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
|
-
|
|
232
|
-
|
|
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
|
-
|
|
258
|
-
|
|
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,
|