feat: implement Redis caching for analytics endpoints with fallback to database
Test / test (push) Successful in 15s

This commit is contained in:
k1nq
2025-11-29 09:45:27 +05:00
parent 31d6a05521
commit fbb3116a2d
15 changed files with 671 additions and 13 deletions
+15 -2
View File
@@ -5,6 +5,7 @@ from collections.abc import Iterable
from dataclasses import dataclass
from decimal import Decimal
from redis.asyncio.client import Redis
from sqlalchemy import func, select
from app.models.activity import Activity, ActivityType
@@ -12,6 +13,7 @@ from app.models.contact import Contact
from app.models.deal import Deal, DealCreate, DealStage, DealStatus
from app.models.organization_member import OrganizationRole
from app.repositories.deal_repo import DealRepository
from app.services.analytics_service import invalidate_analytics_cache
from app.services.organization_service import OrganizationContext
@@ -61,13 +63,23 @@ class DealUpdateData:
class DealService:
"""Encapsulates deal workflows and validations."""
def __init__(self, repository: DealRepository) -> None:
def __init__(
self,
repository: DealRepository,
cache: Redis | None = None,
*,
cache_backoff_ms: int = 0,
) -> None:
self._repository = repository
self._cache = cache
self._cache_backoff_ms = cache_backoff_ms
async def create_deal(self, data: DealCreate, *, context: OrganizationContext) -> Deal:
self._ensure_same_organization(data.organization_id, context)
await self._ensure_contact_in_organization(data.contact_id, context.organization_id)
return await self._repository.create(data=data, role=context.role, user_id=context.user_id)
deal = await self._repository.create(data=data, role=context.role, user_id=context.user_id)
await invalidate_analytics_cache(self._cache, context.organization_id, self._cache_backoff_ms)
return deal
async def update_deal(
self,
@@ -111,6 +123,7 @@ class DealService:
author_id=context.user_id,
activities=[activity for activity in [stage_activity, status_activity] if activity],
)
await invalidate_analytics_cache(self._cache, context.organization_id, self._cache_backoff_ms)
return updated
async def ensure_contact_can_be_deleted(self, contact_id: int) -> None: