You've already forked RekomenciBackend
feat(): profiles
This commit is contained in:
@@ -0,0 +1,20 @@
|
|||||||
|
from typing import override
|
||||||
|
|
||||||
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from template_project.adapters.data_gateways.tables import profile_table
|
||||||
|
from template_project.application.user.entity import UserId
|
||||||
|
from template_project.application.user.profile.data_gateway import ProfileDataGateway
|
||||||
|
from template_project.application.user.profile.entity import Profile
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultProfileDataGateway(ProfileDataGateway):
|
||||||
|
def __init__(self, session: AsyncSession) -> None:
|
||||||
|
self._session = session
|
||||||
|
|
||||||
|
@override
|
||||||
|
async def load_by_user_id(self, user_id: UserId) -> Profile | None:
|
||||||
|
statement = select(Profile).where(profile_table.c.user_id == user_id)
|
||||||
|
result = await self._session.execute(statement)
|
||||||
|
return result.scalar_one_or_none()
|
||||||
@@ -15,6 +15,7 @@ from sqlalchemy.orm import registry
|
|||||||
from template_project.application.access_token.entity import AccessToken
|
from template_project.application.access_token.entity import AccessToken
|
||||||
from template_project.application.auth_identity.entity import AuthIdentity, AuthMethod
|
from template_project.application.auth_identity.entity import AuthIdentity, AuthMethod
|
||||||
from template_project.application.user.entity import User
|
from template_project.application.user.entity import User
|
||||||
|
from template_project.application.user.profile.entity import Profile
|
||||||
|
|
||||||
meta_data = MetaData()
|
meta_data = MetaData()
|
||||||
mapper_registry = registry()
|
mapper_registry = registry()
|
||||||
@@ -23,8 +24,6 @@ user_table = Table(
|
|||||||
"users",
|
"users",
|
||||||
meta_data,
|
meta_data,
|
||||||
Column("id", UUID, primary_key=True),
|
Column("id", UUID, primary_key=True),
|
||||||
Column("email", String, unique=True, nullable=True),
|
|
||||||
Column("hashed_password", String, nullable=True),
|
|
||||||
Column("deleted_at", DateTime(timezone=True)),
|
Column("deleted_at", DateTime(timezone=True)),
|
||||||
Column("created_at", DateTime(timezone=True), nullable=False),
|
Column("created_at", DateTime(timezone=True), nullable=False),
|
||||||
)
|
)
|
||||||
@@ -52,6 +51,22 @@ auth_identity_table = Table(
|
|||||||
UniqueConstraint("method", "identifier", name="uq_auth_method_identifier"),
|
UniqueConstraint("method", "identifier", name="uq_auth_method_identifier"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
profile_table = Table(
|
||||||
|
"profiles",
|
||||||
|
meta_data,
|
||||||
|
Column("id", UUID, primary_key=True),
|
||||||
|
Column("user_id", UUID, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, unique=True),
|
||||||
|
Column("email", String, nullable=True),
|
||||||
|
Column("display_name", String, nullable=True),
|
||||||
|
Column("first_name", String, nullable=True),
|
||||||
|
Column("last_name", String, nullable=True),
|
||||||
|
Column("avatar_url", String, nullable=True),
|
||||||
|
Column("phone", String, nullable=True),
|
||||||
|
Column("deleted_at", DateTime(timezone=True)),
|
||||||
|
Column("created_at", DateTime(timezone=True), nullable=False),
|
||||||
|
)
|
||||||
|
|
||||||
mapper_registry.map_imperatively(User, user_table)
|
mapper_registry.map_imperatively(User, user_table)
|
||||||
mapper_registry.map_imperatively(AccessToken, access_token_table)
|
mapper_registry.map_imperatively(AccessToken, access_token_table)
|
||||||
mapper_registry.map_imperatively(AuthIdentity, auth_identity_table)
|
mapper_registry.map_imperatively(AuthIdentity, auth_identity_table)
|
||||||
|
mapper_registry.map_imperatively(Profile, profile_table)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from template_project.application.common.oauth.yandex import (
|
|||||||
from template_project.application.common.unit_of_work import UnitOfWork
|
from template_project.application.common.unit_of_work import UnitOfWork
|
||||||
from template_project.application.user.entity import User, UserId
|
from template_project.application.user.entity import User, UserId
|
||||||
from template_project.application.user.password_utils import PasswordHasher
|
from template_project.application.user.password_utils import PasswordHasher
|
||||||
|
from template_project.application.user.profile.entity import Profile
|
||||||
|
|
||||||
|
|
||||||
@to_data_structure
|
@to_data_structure
|
||||||
@@ -46,10 +47,7 @@ class SignUpInteractor:
|
|||||||
|
|
||||||
hashed_password = self.password_hasher.hash(password)
|
hashed_password = self.password_hasher.hash(password)
|
||||||
|
|
||||||
user = User.factory(
|
user = User.factory()
|
||||||
email=email,
|
|
||||||
hashed_password=hashed_password,
|
|
||||||
)
|
|
||||||
|
|
||||||
auth_identity = AuthIdentity.factory(
|
auth_identity = AuthIdentity.factory(
|
||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
@@ -61,7 +59,9 @@ class SignUpInteractor:
|
|||||||
access_token = self.access_token_factory.execute(user.id)
|
access_token = self.access_token_factory.execute(user.id)
|
||||||
crypted_access_token = self.access_token_cryptographer.crypto(access_token.id)
|
crypted_access_token = self.access_token_cryptographer.crypto(access_token.id)
|
||||||
|
|
||||||
for entity in (user, auth_identity, access_token):
|
profile = Profile.factory(user_id=user.id, email=email)
|
||||||
|
|
||||||
|
for entity in (user, auth_identity, access_token, profile):
|
||||||
await self.unit_of_work.add(entity)
|
await self.unit_of_work.add(entity)
|
||||||
|
|
||||||
await self.unit_of_work.commit()
|
await self.unit_of_work.commit()
|
||||||
@@ -101,8 +101,11 @@ class SignUpInteractor:
|
|||||||
secret_key=None,
|
secret_key=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
profile = Profile.factory(user_id=user_id)
|
||||||
|
|
||||||
await self.unit_of_work.add(user)
|
await self.unit_of_work.add(user)
|
||||||
await self.unit_of_work.add(auth_identity)
|
await self.unit_of_work.add(auth_identity)
|
||||||
|
await self.unit_of_work.add(profile)
|
||||||
|
|
||||||
access_token = self.access_token_factory.execute(user_id)
|
access_token = self.access_token_factory.execute(user_id)
|
||||||
crypted_access_token = self.access_token_cryptographer.crypto(access_token.id)
|
crypted_access_token = self.access_token_cryptographer.crypto(access_token.id)
|
||||||
|
|||||||
@@ -11,18 +11,9 @@ UserId = NewType("UserId", UUID)
|
|||||||
|
|
||||||
@to_entity
|
@to_entity
|
||||||
class User(Entity[UserId]):
|
class User(Entity[UserId]):
|
||||||
email: str | None
|
|
||||||
hashed_password: str | None
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def factory(
|
def factory(cls) -> Self:
|
||||||
cls,
|
|
||||||
email: str | None = None,
|
|
||||||
hashed_password: str | None = None,
|
|
||||||
) -> Self:
|
|
||||||
return cls(
|
return cls(
|
||||||
id=UserId(uuid7()),
|
id=UserId(uuid7()),
|
||||||
email=email,
|
|
||||||
hashed_password=hashed_password,
|
|
||||||
created_at=datetime.now(tz=UTC),
|
created_at=datetime.now(tz=UTC),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
from abc import abstractmethod
|
||||||
|
from typing import Protocol
|
||||||
|
|
||||||
|
from template_project.application.user.entity import UserId
|
||||||
|
from template_project.application.user.profile.entity import Profile
|
||||||
|
|
||||||
|
|
||||||
|
class ProfileDataGateway(Protocol):
|
||||||
|
@abstractmethod
|
||||||
|
async def load_by_user_id(self, user_id: UserId) -> Profile | None:
|
||||||
|
raise NotImplementedError
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
from datetime import UTC, datetime
|
||||||
|
from typing import NewType, Self
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from uuid_utils.compat import uuid7
|
||||||
|
|
||||||
|
from template_project.application.common.entity import Entity, to_entity
|
||||||
|
from template_project.application.user.entity import UserId
|
||||||
|
|
||||||
|
ProfileId = NewType("ProfileId", UUID)
|
||||||
|
|
||||||
|
|
||||||
|
@to_entity
|
||||||
|
class Profile(Entity[ProfileId]):
|
||||||
|
user_id: UserId
|
||||||
|
email: str | None
|
||||||
|
display_name: str | None
|
||||||
|
first_name: str | None
|
||||||
|
last_name: str | None
|
||||||
|
avatar_url: str | None
|
||||||
|
phone: str | None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def factory(
|
||||||
|
cls,
|
||||||
|
user_id: UserId,
|
||||||
|
email: str | None = None,
|
||||||
|
display_name: str | None = None,
|
||||||
|
first_name: str | None = None,
|
||||||
|
last_name: str | None = None,
|
||||||
|
avatar_url: str | None = None,
|
||||||
|
phone: str | None = None,
|
||||||
|
) -> Self:
|
||||||
|
return cls(
|
||||||
|
id=ProfileId(uuid7()),
|
||||||
|
user_id=user_id,
|
||||||
|
email=email,
|
||||||
|
display_name=display_name,
|
||||||
|
first_name=first_name,
|
||||||
|
last_name=last_name,
|
||||||
|
avatar_url=avatar_url,
|
||||||
|
phone=phone,
|
||||||
|
created_at=datetime.now(tz=UTC),
|
||||||
|
)
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
from template_project.application.common.data_structure import to_data_structure
|
||||||
|
from template_project.application.common.identity_provider import IdentityProvider
|
||||||
|
from template_project.application.common.interactor import to_interactor
|
||||||
|
from template_project.application.user.entity import UserId
|
||||||
|
from template_project.application.user.profile.data_gateway import ProfileDataGateway
|
||||||
|
|
||||||
|
|
||||||
|
@to_data_structure
|
||||||
|
class GetProfileResponse:
|
||||||
|
id: UserId
|
||||||
|
email: str | None
|
||||||
|
display_name: str | None
|
||||||
|
first_name: str | None
|
||||||
|
last_name: str | None
|
||||||
|
avatar_url: str | None
|
||||||
|
phone: str | None
|
||||||
|
|
||||||
|
|
||||||
|
@to_interactor
|
||||||
|
class GetProfileInteractor:
|
||||||
|
identity_provider: IdentityProvider
|
||||||
|
profile_data_gateway: ProfileDataGateway
|
||||||
|
|
||||||
|
async def execute(self) -> GetProfileResponse | None:
|
||||||
|
current_user = await self.identity_provider.get_current_user()
|
||||||
|
profile = await self.profile_data_gateway.load_by_user_id(current_user.id)
|
||||||
|
|
||||||
|
if not profile:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return GetProfileResponse(
|
||||||
|
id=profile.user_id,
|
||||||
|
email=profile.email,
|
||||||
|
display_name=profile.display_name,
|
||||||
|
first_name=profile.first_name,
|
||||||
|
last_name=profile.last_name,
|
||||||
|
avatar_url=profile.avatar_url,
|
||||||
|
phone=profile.phone,
|
||||||
|
)
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
from template_project.application.common.data_structure import to_data_structure
|
||||||
|
from template_project.application.common.identity_provider import IdentityProvider
|
||||||
|
from template_project.application.common.interactor import to_interactor
|
||||||
|
from template_project.application.common.unit_of_work import UnitOfWork
|
||||||
|
from template_project.application.user.entity import UserId
|
||||||
|
from template_project.application.user.profile.data_gateway import ProfileDataGateway
|
||||||
|
from template_project.application.user.profile.entity import Profile
|
||||||
|
|
||||||
|
|
||||||
|
@to_data_structure
|
||||||
|
class PatchProfileRequest:
|
||||||
|
email: str | None = None
|
||||||
|
display_name: str | None = None
|
||||||
|
first_name: str | None = None
|
||||||
|
last_name: str | None = None
|
||||||
|
avatar_url: str | None = None
|
||||||
|
phone: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@to_data_structure
|
||||||
|
class PatchProfileResponse:
|
||||||
|
id: UserId
|
||||||
|
email: str | None
|
||||||
|
display_name: str | None
|
||||||
|
first_name: str | None
|
||||||
|
last_name: str | None
|
||||||
|
avatar_url: str | None
|
||||||
|
phone: str | None
|
||||||
|
|
||||||
|
|
||||||
|
@to_interactor
|
||||||
|
class PatchProfileInteractor:
|
||||||
|
identity_provider: IdentityProvider
|
||||||
|
profile_data_gateway: ProfileDataGateway
|
||||||
|
unit_of_work: UnitOfWork
|
||||||
|
|
||||||
|
async def execute(self, request: PatchProfileRequest) -> PatchProfileResponse:
|
||||||
|
current_user = await self.identity_provider.get_current_user()
|
||||||
|
profile = await self.profile_data_gateway.load_by_user_id(current_user.id)
|
||||||
|
|
||||||
|
if profile:
|
||||||
|
for attr in ("email", "display_name", "first_name", "last_name", "avatar_url", "phone"):
|
||||||
|
value = getattr(request, attr)
|
||||||
|
if value is not None:
|
||||||
|
setattr(profile, attr, value)
|
||||||
|
|
||||||
|
await self.unit_of_work.add(profile)
|
||||||
|
else:
|
||||||
|
profile = Profile.factory(
|
||||||
|
user_id=current_user.id,
|
||||||
|
email=request.email,
|
||||||
|
display_name=request.display_name,
|
||||||
|
first_name=request.first_name,
|
||||||
|
last_name=request.last_name,
|
||||||
|
avatar_url=request.avatar_url,
|
||||||
|
phone=request.phone,
|
||||||
|
)
|
||||||
|
await self.unit_of_work.add(profile)
|
||||||
|
|
||||||
|
await self.unit_of_work.commit()
|
||||||
|
|
||||||
|
return PatchProfileResponse(
|
||||||
|
id=profile.user_id,
|
||||||
|
email=profile.email,
|
||||||
|
display_name=profile.display_name,
|
||||||
|
first_name=profile.first_name,
|
||||||
|
last_name=profile.last_name,
|
||||||
|
avatar_url=profile.avatar_url,
|
||||||
|
phone=profile.phone,
|
||||||
|
)
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: b5fa4f3e95c5
|
||||||
|
Revises: f29dccaa6356
|
||||||
|
Create Date: 2025-11-20 01:13:13.877236
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = 'b5fa4f3e95c5'
|
||||||
|
down_revision: Union[str, Sequence[str], None] = 'f29dccaa6356'
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
"""Upgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('profiles',
|
||||||
|
sa.Column('id', sa.UUID(), nullable=False),
|
||||||
|
sa.Column('user_id', sa.UUID(), nullable=False),
|
||||||
|
sa.Column('email', sa.String(), nullable=True),
|
||||||
|
sa.Column('display_name', sa.String(), nullable=True),
|
||||||
|
sa.Column('first_name', sa.String(), nullable=True),
|
||||||
|
sa.Column('last_name', sa.String(), nullable=True),
|
||||||
|
sa.Column('avatar_url', sa.String(), nullable=True),
|
||||||
|
sa.Column('phone', sa.String(), nullable=True),
|
||||||
|
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
|
||||||
|
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('user_id')
|
||||||
|
)
|
||||||
|
op.drop_constraint(op.f('users_email_key'), 'users', type_='unique')
|
||||||
|
op.drop_column('users', 'hashed_password')
|
||||||
|
op.drop_column('users', 'email')
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('users', sa.Column('email', sa.VARCHAR(), autoincrement=False, nullable=True))
|
||||||
|
op.add_column('users', sa.Column('hashed_password', sa.VARCHAR(), autoincrement=False, nullable=True))
|
||||||
|
op.create_unique_constraint(op.f('users_email_key'), 'users', ['email'], postgresql_nulls_not_distinct=False)
|
||||||
|
op.drop_table('profiles')
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -16,7 +16,7 @@ from fastapi.middleware.cors import CORSMiddleware
|
|||||||
|
|
||||||
from template_project.web_api.configuration import load_configuration
|
from template_project.web_api.configuration import load_configuration
|
||||||
from template_project.web_api.ioc.make import make_ioc
|
from template_project.web_api.ioc.make import make_ioc
|
||||||
from template_project.web_api.routes import auth, healthcheck
|
from template_project.web_api.routes import auth, healthcheck, profile
|
||||||
|
|
||||||
LOG_CONFIG: Final = {
|
LOG_CONFIG: Final = {
|
||||||
"version": 1,
|
"version": 1,
|
||||||
@@ -65,6 +65,7 @@ def make_asgi_application(
|
|||||||
)
|
)
|
||||||
app.include_router(auth.router)
|
app.include_router(auth.router)
|
||||||
app.include_router(healthcheck.router)
|
app.include_router(healthcheck.router)
|
||||||
|
app.include_router(profile.router)
|
||||||
|
|
||||||
setup_dishka(container=ioc, app=app)
|
setup_dishka(container=ioc, app=app)
|
||||||
|
|
||||||
|
|||||||
@@ -32,9 +32,7 @@ class WebApiIdentityProvider(IdentityProvider):
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
async def get_current_user(self) -> User:
|
async def get_current_user(self) -> User:
|
||||||
auth_tokn = self._request.headers[AUTH_HEADER]
|
access_token_id = self._parse_token()
|
||||||
|
|
||||||
access_token_id = self._parse_token(auth_tokn)
|
|
||||||
if access_token_id is None:
|
if access_token_id is None:
|
||||||
raise UserUnauthorizedError
|
raise UserUnauthorizedError
|
||||||
|
|
||||||
@@ -54,7 +52,7 @@ class WebApiIdentityProvider(IdentityProvider):
|
|||||||
|
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def _parse_token(self, token: str) -> AccessTokenId | None:
|
def _parse_token(self) -> AccessTokenId | None:
|
||||||
authorization_header = self._request.headers.get(AUTH_HEADER)
|
authorization_header = self._request.headers.get(AUTH_HEADER)
|
||||||
|
|
||||||
if authorization_header is None:
|
if authorization_header is None:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from dishka import BaseScope, Provider, Scope, WithParents, provide, provide_all
|
|||||||
|
|
||||||
from template_project.adapters.data_gateways.access_token import DefaultAccessTokenDataGateway
|
from template_project.adapters.data_gateways.access_token import DefaultAccessTokenDataGateway
|
||||||
from template_project.adapters.data_gateways.auth_identity import DefaultAuthIdentityDataGateway
|
from template_project.adapters.data_gateways.auth_identity import DefaultAuthIdentityDataGateway
|
||||||
|
from template_project.adapters.data_gateways.profile import DefaultProfileDataGateway
|
||||||
from template_project.adapters.data_gateways.user import DefaultUserDataGateway
|
from template_project.adapters.data_gateways.user import DefaultUserDataGateway
|
||||||
from template_project.adapters.unit_of_work import DefaultUnitOfWork
|
from template_project.adapters.unit_of_work import DefaultUnitOfWork
|
||||||
|
|
||||||
@@ -14,4 +15,5 @@ class DataGatewayProvider(Provider):
|
|||||||
WithParents[DefaultUserDataGateway],
|
WithParents[DefaultUserDataGateway],
|
||||||
WithParents[DefaultAccessTokenDataGateway],
|
WithParents[DefaultAccessTokenDataGateway],
|
||||||
WithParents[DefaultAuthIdentityDataGateway],
|
WithParents[DefaultAuthIdentityDataGateway],
|
||||||
|
WithParents[DefaultProfileDataGateway],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from dishka import BaseScope, Provider, Scope, provide
|
from dishka import BaseScope, Provider, Scope, WithParents, provide
|
||||||
|
|
||||||
from template_project.web_api.identity_provider import WebApiIdentityProvider
|
from template_project.web_api.identity_provider import WebApiIdentityProvider
|
||||||
|
|
||||||
@@ -6,4 +6,4 @@ from template_project.web_api.identity_provider import WebApiIdentityProvider
|
|||||||
class IdPProvider(Provider):
|
class IdPProvider(Provider):
|
||||||
scope: BaseScope | None = Scope.REQUEST
|
scope: BaseScope | None = Scope.REQUEST
|
||||||
|
|
||||||
web_api = provide(WebApiIdentityProvider)
|
web_api = provide(WithParents[WebApiIdentityProvider])
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ from dishka import BaseScope, Provider, Scope, provide_all
|
|||||||
|
|
||||||
from template_project.application.auth_identity.interactors.sign_in import SignInInteractor
|
from template_project.application.auth_identity.interactors.sign_in import SignInInteractor
|
||||||
from template_project.application.auth_identity.interactors.sign_up import SignUpInteractor
|
from template_project.application.auth_identity.interactors.sign_up import SignUpInteractor
|
||||||
|
from template_project.application.user.profile.interactors.get_profile import GetProfileInteractor
|
||||||
|
from template_project.application.user.profile.interactors.patch_profile import PatchProfileInteractor
|
||||||
|
|
||||||
|
|
||||||
class InteractorProvider(Provider):
|
class InteractorProvider(Provider):
|
||||||
@@ -10,4 +12,6 @@ class InteractorProvider(Provider):
|
|||||||
interactors = provide_all(
|
interactors = provide_all(
|
||||||
SignInInteractor,
|
SignInInteractor,
|
||||||
SignUpInteractor,
|
SignUpInteractor,
|
||||||
|
GetProfileInteractor,
|
||||||
|
PatchProfileInteractor,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
from dishka import FromDishka
|
||||||
|
from dishka.integrations.fastapi import DishkaRoute
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
from fastapi.security import HTTPBearer
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from template_project.application.user.entity import UserId
|
||||||
|
from template_project.application.user.profile.interactors.get_profile import GetProfileInteractor
|
||||||
|
from template_project.application.user.profile.interactors.patch_profile import (
|
||||||
|
PatchProfileInteractor,
|
||||||
|
PatchProfileRequest,
|
||||||
|
)
|
||||||
|
|
||||||
|
security = HTTPBearer()
|
||||||
|
router = APIRouter(route_class=DishkaRoute, dependencies=[Depends(security)])
|
||||||
|
|
||||||
|
|
||||||
|
class GetProfileResponse(BaseModel):
|
||||||
|
id: UserId
|
||||||
|
email: str | None
|
||||||
|
display_name: str | None
|
||||||
|
first_name: str | None
|
||||||
|
last_name: str | None
|
||||||
|
avatar_url: str | None
|
||||||
|
phone: str | None
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/profile")
|
||||||
|
async def get_profile(
|
||||||
|
interactor: FromDishka[GetProfileInteractor],
|
||||||
|
) -> GetProfileResponse:
|
||||||
|
response = await interactor.execute()
|
||||||
|
if not response:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Profile not found")
|
||||||
|
return GetProfileResponse(
|
||||||
|
id=response.id,
|
||||||
|
email=response.email,
|
||||||
|
display_name=response.display_name,
|
||||||
|
first_name=response.first_name,
|
||||||
|
last_name=response.last_name,
|
||||||
|
avatar_url=response.avatar_url,
|
||||||
|
phone=response.phone,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PatchProfileRequestModel(BaseModel):
|
||||||
|
email: str | None = None
|
||||||
|
display_name: str | None = None
|
||||||
|
first_name: str | None = None
|
||||||
|
last_name: str | None = None
|
||||||
|
avatar_url: str | None = None
|
||||||
|
phone: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class PatchProfileResponse(BaseModel):
|
||||||
|
id: UserId
|
||||||
|
email: str | None
|
||||||
|
display_name: str | None
|
||||||
|
first_name: str | None
|
||||||
|
last_name: str | None
|
||||||
|
avatar_url: str | None
|
||||||
|
phone: str | None
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch("/profile")
|
||||||
|
async def patch_profile(
|
||||||
|
request: PatchProfileRequestModel,
|
||||||
|
interactor: FromDishka[PatchProfileInteractor],
|
||||||
|
) -> PatchProfileResponse:
|
||||||
|
patch_request = PatchProfileRequest(
|
||||||
|
email=request.email,
|
||||||
|
display_name=request.display_name,
|
||||||
|
first_name=request.first_name,
|
||||||
|
last_name=request.last_name,
|
||||||
|
avatar_url=request.avatar_url,
|
||||||
|
phone=request.phone,
|
||||||
|
)
|
||||||
|
response = await interactor.execute(patch_request)
|
||||||
|
return PatchProfileResponse(
|
||||||
|
id=response.id,
|
||||||
|
email=response.email,
|
||||||
|
display_name=response.display_name,
|
||||||
|
first_name=response.first_name,
|
||||||
|
last_name=response.last_name,
|
||||||
|
avatar_url=response.avatar_url,
|
||||||
|
phone=response.phone,
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user