From e0df49849ac597cde0fef723eee3f5964c31d728 Mon Sep 17 00:00:00 2001 From: vadym Date: Wed, 3 Sep 2025 22:54:23 +0300 Subject: [PATCH] hw lesson 23 --- Dockerfile | 10 +++++----- docker-compose.yml | 2 -- src/analytics/__init__.py | 0 src/analytics/models/mongo.py | 20 ++++++++++++++++++++ src/analytics/repositories.py | 6 ++++++ src/analytics/services.py | 22 ++++++++++++++++++++++ src/catalogue/views/product.py | 3 +++ src/common/databases/mongo_db.py | 2 ++ 8 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 src/analytics/__init__.py create mode 100644 src/analytics/models/mongo.py create mode 100644 src/analytics/repositories.py create mode 100644 src/analytics/services.py diff --git a/Dockerfile b/Dockerfile index c1410224..8018efc3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,13 +4,13 @@ WORKDIR /app COPY pyproject.toml pyproject.toml -RUN pip install poetry -RUN poetry config virtualenvs.create false -RUN poetry install --no-dev +RUN pip install --no-cache-dir poetry \ + && poetry config virtualenvs.create false + +RUN poetry install --no-root --no-interaction --no-ansi COPY . /app RUN chmod +x ops/start-api.sh - -CMD ["sh", "ops/start-api.sh"] +CMD ["sh", "ops/start-api.sh"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index c12fd799..ccb6c2fa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3.8' - services: web: build: . diff --git a/src/analytics/__init__.py b/src/analytics/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/analytics/models/mongo.py b/src/analytics/models/mongo.py new file mode 100644 index 00000000..4bfe0133 --- /dev/null +++ b/src/analytics/models/mongo.py @@ -0,0 +1,20 @@ +from datetime import datetime, timezone + +from beanie import Document +from pydantic import BaseModel + + +class BaseProductAnalytics(BaseModel): + product_id: int + timestamp: datetime + + +class ProductAnalytics(Document, BaseProductAnalytics): + class Settings: + name = 'productAnalytics' + + @classmethod + def new_visit(cls, product_id: int, timestamp: datetime | None = None) -> "ProductAnalytics": + if timestamp is None: + timestamp = datetime.now(timezone.utc) + return cls(product_id=product_id, timestamp=timestamp) diff --git a/src/analytics/repositories.py b/src/analytics/repositories.py new file mode 100644 index 00000000..2789df2c --- /dev/null +++ b/src/analytics/repositories.py @@ -0,0 +1,6 @@ +from src.common.repository.beanie import BaseMongoRepository +from src.analytics.models.mongo import ProductAnalytics + + +class ProductAnalyticsRepository(BaseMongoRepository[ProductAnalytics]): + __model__ = ProductAnalytics diff --git a/src/analytics/services.py b/src/analytics/services.py new file mode 100644 index 00000000..9c11e543 --- /dev/null +++ b/src/analytics/services.py @@ -0,0 +1,22 @@ +from datetime import datetime, timezone +from typing import Annotated, Optional + +from fastapi import Depends + +from src.common.service import BaseService +from src.analytics.models.mongo import ProductAnalytics +from src.analytics.repositories import ProductAnalyticsRepository + + +class ProductAnalyticsService(BaseService): + def __init__( + self, + repository: Annotated[ProductAnalyticsRepository, Depends(ProductAnalyticsRepository)], + ): + super().__init__(repository=repository) + + async def record_visit(self, product_id: int, timestamp: Optional[datetime] = None) -> ProductAnalytics: + if timestamp is None: + timestamp = datetime.now(timezone.utc) + instance = ProductAnalytics(product_id=product_id, timestamp=timestamp) + return await self.repository.create(instance=instance) diff --git a/src/catalogue/views/product.py b/src/catalogue/views/product.py index 5b05453d..bfd484f3 100644 --- a/src/catalogue/views/product.py +++ b/src/catalogue/views/product.py @@ -16,6 +16,7 @@ ProductRoutesPrefixes, ) from src.catalogue.services import get_product_service +from src.analytics.services import ProductAnalyticsService from src.common.exceptions.base import ObjectDoesNotExistException from src.common.schemas.common import ErrorResponse @@ -51,6 +52,7 @@ async def product_detail( response: Response, pk: int, service: Annotated[get_product_service, Depends()], + analytics_service: Annotated[ProductAnalyticsService, Depends()], ) -> Union[Response, ErrorResponse]: """ Retrieve product. @@ -60,6 +62,7 @@ async def product_detail( """ try: response = await service.detail(pk=pk) + await analytics_service.record_visit(product_id=pk) except ObjectDoesNotExistException as exc: response.status_code = status.HTTP_404_NOT_FOUND return ErrorResponse(message=exc.message) diff --git a/src/common/databases/mongo_db.py b/src/common/databases/mongo_db.py index 6667f098..e531324c 100644 --- a/src/common/databases/mongo_db.py +++ b/src/common/databases/mongo_db.py @@ -7,6 +7,7 @@ from src.base_settings import base_settings from src.common.singleton import SingletonMeta from src.reviews.models.mongo import ProductReview +from src.analytics.models.mongo import ProductAnalytics class AsyncMongoDBClient(AsyncIOMotorClient, metaclass=SingletonMeta): @@ -21,5 +22,6 @@ async def init_mongo_db(): database=client.get_database(), document_models=[ ProductReview, + ProductAnalytics, ], )