From efdacfa0ce087d24be37be9eaa34e52763b86069 Mon Sep 17 00:00:00 2001 From: Sergey Vanyushkin Date: Mon, 8 Apr 2024 14:32:50 +0000 Subject: [PATCH] add company domain, models, route --- api/app_builder/routers.py | 4 +- api/application/contracts/company/__init__.py | 0 .../contracts/company/company_request.py | 6 ++ .../contracts/company/company_response.py | 7 +++ api/domain/company/__init__.py | 0 api/domain/company/error.py | 10 ++++ api/domain/company/model.py | 57 +++++++++++++++++++ api/domain/company/repository.py | 15 +++++ api/infrastructure/auth/jwt_processor.py | 15 +++-- api/presentation/auth/fasapi_auth.py | 1 - api/presentation/routers/__init__.py | 2 + api/presentation/routers/company.py | 20 +++++++ 12 files changed, 131 insertions(+), 6 deletions(-) create mode 100644 api/application/contracts/company/__init__.py create mode 100644 api/application/contracts/company/company_request.py create mode 100644 api/application/contracts/company/company_response.py create mode 100644 api/domain/company/__init__.py create mode 100644 api/domain/company/error.py create mode 100644 api/domain/company/model.py create mode 100644 api/domain/company/repository.py create mode 100644 api/presentation/routers/company.py diff --git a/api/app_builder/routers.py b/api/app_builder/routers.py index ac62dbf..714fbac 100644 --- a/api/app_builder/routers.py +++ b/api/app_builder/routers.py @@ -1,9 +1,11 @@ from fastapi import FastAPI -from api.presentation.routers import auth_router, healthcheck_router, user_router +from api.presentation.routers import (auth_router, company_router, + healthcheck_router, user_router) def init_routers(app: FastAPI) -> None: app.include_router(user_router) app.include_router(auth_router) + app.include_router(company_router) app.include_router(healthcheck_router) diff --git a/api/application/contracts/company/__init__.py b/api/application/contracts/company/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/application/contracts/company/company_request.py b/api/application/contracts/company/company_request.py new file mode 100644 index 0000000..7f37c0e --- /dev/null +++ b/api/application/contracts/company/company_request.py @@ -0,0 +1,6 @@ +from dataclasses import dataclass + + +@dataclass(frozen=True) +class CompanyByOwnerEmail: + email: str diff --git a/api/application/contracts/company/company_response.py b/api/application/contracts/company/company_response.py new file mode 100644 index 0000000..b678174 --- /dev/null +++ b/api/application/contracts/company/company_response.py @@ -0,0 +1,7 @@ +from dataclasses import dataclass + + +@dataclass(frozen=True) +class CompanyBaseResponse: + name: str + email: str diff --git a/api/domain/company/__init__.py b/api/domain/company/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/domain/company/error.py b/api/domain/company/error.py new file mode 100644 index 0000000..984cb10 --- /dev/null +++ b/api/domain/company/error.py @@ -0,0 +1,10 @@ +from api.domain import DomainError + + +class CompanyNotFoundError(DomainError): ... + + +class CompanyAlreadyExistsError(DomainError): ... + + +class UserAccessError(DomainError): ... diff --git a/api/domain/company/model.py b/api/domain/company/model.py new file mode 100644 index 0000000..018f1b2 --- /dev/null +++ b/api/domain/company/model.py @@ -0,0 +1,57 @@ +import re +from dataclasses import dataclass +from uuid import UUID, uuid4 + +from api.domain import DomainValidationError +from api.domain.entity import DomainEntity +from api.domain.user.model import User, UserId +from api.domain.value_obj import DomainValueObject + + +@dataclass(frozen=True) +class CompanyEmail(DomainValueObject): + value: str + + def __post_init__(self) -> None: + pattern = r"^[\w\.-]+@[a-zA-Z\d\.-]+\.[a-zA-Z]{2,}$" + + if not re.match(pattern, self.value): + raise DomainValidationError( + "Invalid email format. Email must be in the format 'example@example.com'." + ) + + +@dataclass(frozen=True) +class CompanyName(DomainValueObject): + value: str + + def __post_init__(self) -> None: + if len(self.value) < 1: + raise DomainValidationError("First name must be at least 1 character long.") + if len(self.value) > 100: + raise DomainValidationError( + "First name must be at most 100 characters long." + ) + if not self.value.isalpha(): + raise DomainValidationError("First name must only contain letters.") + + +@dataclass(frozen=True) +class CompanyId(DomainValueObject): + value: UUID + + +@dataclass +class Company(DomainEntity[CompanyId]): + name: CompanyName + email: CompanyEmail + owner: User + + @staticmethod + def create(name: str, email: str, owner: User) -> "Company": + return Company( + id=CompanyId(uuid4()), + name=CompanyName(name), + email=CompanyEmail(email), + owner=owner, + ) diff --git a/api/domain/company/repository.py b/api/domain/company/repository.py new file mode 100644 index 0000000..390dfd5 --- /dev/null +++ b/api/domain/company/repository.py @@ -0,0 +1,15 @@ +from typing import Protocol + +from api.domain.company.model import Company +from api.domain.user.model import User + + +class CompanyRepository(Protocol): + async def get_company(self, filter: dict) -> User | None: + raise NotImplementedError + + async def create_company(self, company: Company) -> None: + raise NotImplementedError + + async def get_workes_list(self) -> list[User] | None: + raise NotImplementedError diff --git a/api/infrastructure/auth/jwt_processor.py b/api/infrastructure/auth/jwt_processor.py index d9860f1..7ed460b 100644 --- a/api/infrastructure/auth/jwt_processor.py +++ b/api/infrastructure/auth/jwt_processor.py @@ -6,12 +6,15 @@ from jose.jwt import decode, encode from api.application.protocols.date_time import DateTimeProvider from api.application.protocols.jwt import JwtTokenProcessor +from api.domain.user.error import UserInvalidCredentialsError from api.domain.user.model import UserId from api.infrastructure.auth.jwt_settings import JwtSettings class JoseJwtTokenProcessor(JwtTokenProcessor): - def __init__(self, jwt_options: JwtSettings, date_time_provider: DateTimeProvider) -> None: + def __init__( + self, jwt_options: JwtSettings, date_time_provider: DateTimeProvider + ) -> None: self.jwt_options = jwt_options self.date_time_provider = date_time_provider @@ -29,11 +32,15 @@ class JoseJwtTokenProcessor(JwtTokenProcessor): def validate_token(self, token: str) -> UserId | None: try: - payload = decode(token, self.jwt_options.secret, [self.jwt_options.algorithm]) + payload = decode( + token, self.jwt_options.secret, [self.jwt_options.algorithm] + ) return UserId(UUID(payload["sub"])) - except (JWTError, ValueError, KeyError): return None def refresh_token(self, token: str) -> str: - return "" + user = self.validate_token(token) + if user is None: + raise UserInvalidCredentialsError("invalid token") + return self.generate_token(user) diff --git a/api/presentation/auth/fasapi_auth.py b/api/presentation/auth/fasapi_auth.py index fc391cf..0101bb2 100644 --- a/api/presentation/auth/fasapi_auth.py +++ b/api/presentation/auth/fasapi_auth.py @@ -36,7 +36,6 @@ class OAuth2PasswordBearerWithCookie(OAuth2): ) else: return None - print(param) return param diff --git a/api/presentation/routers/__init__.py b/api/presentation/routers/__init__.py index 3dc43b7..b8e127b 100644 --- a/api/presentation/routers/__init__.py +++ b/api/presentation/routers/__init__.py @@ -1,4 +1,5 @@ from .auth import auth_router +from .company import company_router from .ping import healthcheck_router from .user import user_router @@ -6,4 +7,5 @@ __all__ = ( "healthcheck_router", "auth_router", "user_router", + "company_router", ) diff --git a/api/presentation/routers/company.py b/api/presentation/routers/company.py new file mode 100644 index 0000000..4d7f038 --- /dev/null +++ b/api/presentation/routers/company.py @@ -0,0 +1,20 @@ +from fastapi import APIRouter, Depends, Request + +from api.application.contracts.company.company_response import \ + CompanyBaseResponse +from api.presentation.auth.fasapi_auth import auth_required + +company_router = APIRouter(prefix="/company", tags=["Company"]) + + +@company_router.get( + "/", + response_model=None, + dependencies=[Depends(auth_required)], +) +async def get_company(request: Request) -> CompanyBaseResponse: + print(request.scope["auth"]) + return CompanyBaseResponse( + name="some", + email="some", + )