feat(): profiles

This commit is contained in:
doas root
2025-11-20 01:37:19 +03:00
parent 8c7ce13922
commit 52f3072729
17 changed files with 361 additions and 24 deletions
+2 -1
View File
@@ -16,7 +16,7 @@ from fastapi.middleware.cors import CORSMiddleware
from template_project.web_api.configuration import load_configuration
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 = {
"version": 1,
@@ -65,6 +65,7 @@ def make_asgi_application(
)
app.include_router(auth.router)
app.include_router(healthcheck.router)
app.include_router(profile.router)
setup_dishka(container=ioc, app=app)
@@ -32,9 +32,7 @@ class WebApiIdentityProvider(IdentityProvider):
@override
async def get_current_user(self) -> User:
auth_tokn = self._request.headers[AUTH_HEADER]
access_token_id = self._parse_token(auth_tokn)
access_token_id = self._parse_token()
if access_token_id is None:
raise UserUnauthorizedError
@@ -54,7 +52,7 @@ class WebApiIdentityProvider(IdentityProvider):
return user
def _parse_token(self, token: str) -> AccessTokenId | None:
def _parse_token(self) -> AccessTokenId | None:
authorization_header = self._request.headers.get(AUTH_HEADER)
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.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.unit_of_work import DefaultUnitOfWork
@@ -14,4 +15,5 @@ class DataGatewayProvider(Provider):
WithParents[DefaultUserDataGateway],
WithParents[DefaultAccessTokenDataGateway],
WithParents[DefaultAuthIdentityDataGateway],
WithParents[DefaultProfileDataGateway],
)
+2 -2
View File
@@ -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
@@ -6,4 +6,4 @@ from template_project.web_api.identity_provider import WebApiIdentityProvider
class IdPProvider(Provider):
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_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):
@@ -10,4 +12,6 @@ class InteractorProvider(Provider):
interactors = provide_all(
SignInInteractor,
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,
)