This commit is contained in:
2025-12-23 22:20:42 +03:00
parent 71e8d1079e
commit 493c385cb1
31 changed files with 574 additions and 507 deletions
@@ -0,0 +1,28 @@
"""Add premium fields to users
Revision ID: 002
Revises: 001
Create Date: 2024-01-02 00:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
revision = '002'
down_revision = '001'
branch_labels = None
depends_on = None
def upgrade() -> None:
op.add_column('users', sa.Column('is_premium', sa.Boolean(), nullable=False, server_default='false'))
op.add_column('users', sa.Column('premium_until', sa.DateTime(), nullable=True))
op.add_column('users', sa.Column('questions_used', sa.Integer(), nullable=False, server_default='0'))
def downgrade() -> None:
op.drop_column('users', 'questions_used')
op.drop_column('users', 'premium_until')
op.drop_column('users', 'is_premium')
+1 -1
View File
@@ -1,4 +1,4 @@
fastapi==0.104.1
fastapi==0.100.1
uvicorn[standard]==0.24.0
sqlalchemy[asyncio]==2.0.23
asyncpg==0.29.0
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env python3
import sys
import os
from pathlib import Path
backend_dir = Path(__file__).parent
sys.path.insert(0, str(backend_dir))
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"src.presentation.main:app",
host="0.0.0.0",
port=8000,
reload=True,
log_level="info"
)
@@ -3,6 +3,7 @@ Use cases для работы с пользователями
"""
from uuid import UUID
from typing import Optional
from datetime import datetime, timedelta
from src.domain.entities.user import User, UserRole
from src.domain.repositories.user_repository import IUserRepository
from src.shared.exceptions import NotFoundError, ValidationError
@@ -52,4 +53,27 @@ class UserUseCases:
async def list_users(self, skip: int = 0, limit: int = 100) -> list[User]:
"""Получить список пользователей"""
return await self.user_repository.list_all(skip=skip, limit=limit)
async def increment_questions_used(self, telegram_id: str) -> User:
"""Увеличить счетчик использованных вопросов"""
user = await self.user_repository.get_by_telegram_id(telegram_id)
if not user:
raise NotFoundError(f"Пользователь с telegram_id {telegram_id} не найден")
user.questions_used += 1
return await self.user_repository.update(user)
async def activate_premium(self, telegram_id: str, days: int = 30) -> User:
"""Активировать premium статус"""
user = await self.user_repository.get_by_telegram_id(telegram_id)
if not user:
raise NotFoundError(f"Пользователь с telegram_id {telegram_id} не найден")
user.is_premium = True
if user.premium_until and user.premium_until > datetime.utcnow():
user.premium_until = user.premium_until + timedelta(days=days)
else:
user.premium_until = datetime.utcnow() + timedelta(days=days)
return await self.user_repository.update(user)
+7 -1
View File
@@ -20,12 +20,18 @@ class User:
telegram_id: str,
role: UserRole = UserRole.USER,
user_id: UUID | None = None,
created_at: datetime | None = None
created_at: datetime | None = None,
is_premium: bool = False,
premium_until: datetime | None = None,
questions_used: int = 0
):
self.user_id = user_id or uuid4()
self.telegram_id = telegram_id
self.role = role
self.created_at = created_at or datetime.utcnow()
self.is_premium = is_premium
self.premium_until = premium_until
self.questions_used = questions_used
def is_admin(self) -> bool:
"""проверка, является ли пользователь администратором"""
@@ -17,6 +17,10 @@ class UserModel(Base):
telegram_id = Column(String, unique=True, nullable=False, index=True)
role = Column(String, nullable=False, default="user")
created_at = Column(DateTime, nullable=False, default=datetime.utcnow)
is_premium = Column(Boolean, default=False, nullable=False)
premium_until = Column(DateTime, nullable=True)
questions_used = Column(Integer, default=0, nullable=False)
collections = relationship("CollectionModel", back_populates="owner", cascade="all, delete-orphan")
conversations = relationship("ConversationModel", back_populates="user", cascade="all, delete-orphan")
collection_accesses = relationship("CollectionAccessModel", back_populates="user", cascade="all, delete-orphan")
@@ -23,7 +23,10 @@ class PostgreSQLUserRepository(IUserRepository):
user_id=user.user_id,
telegram_id=user.telegram_id,
role=user.role.value,
created_at=user.created_at
created_at=user.created_at,
is_premium=user.is_premium,
premium_until=user.premium_until,
questions_used=user.questions_used
)
self.session.add(db_user)
await self.session.commit()
@@ -57,6 +60,9 @@ class PostgreSQLUserRepository(IUserRepository):
db_user.telegram_id = user.telegram_id
db_user.role = user.role.value
db_user.is_premium = user.is_premium
db_user.premium_until = user.premium_until
db_user.questions_used = user.questions_used
await self.session.commit()
await self.session.refresh(db_user)
return self._to_entity(db_user)
@@ -90,6 +96,9 @@ class PostgreSQLUserRepository(IUserRepository):
user_id=db_user.user_id,
telegram_id=db_user.telegram_id,
role=UserRole(db_user.role),
created_at=db_user.created_at
created_at=db_user.created_at,
is_premium=db_user.is_premium,
premium_until=db_user.premium_until,
questions_used=db_user.questions_used
)
+4 -4
View File
@@ -24,8 +24,8 @@ router = APIRouter(prefix="/admin", tags=["admin"])
async def admin_list_users(
skip: int = 0,
limit: int = 100,
current_user: FromDishka[User] = FromDishka(),
use_cases: FromDishka[UserUseCases] = FromDishka()
current_user: User = FromDishka(),
use_cases: UserUseCases = FromDishka()
):
"""Получить список всех пользователей (только для админов)"""
if not current_user.is_admin():
@@ -38,8 +38,8 @@ async def admin_list_users(
async def admin_list_collections(
skip: int = 0,
limit: int = 100,
current_user: FromDishka[User] = FromDishka(),
use_cases: FromDishka[CollectionUseCases] = FromDishka()
current_user: User = FromDishka(),
use_cases: CollectionUseCases = FromDishka()
):
"""Получить список всех коллекций (только для админов)"""
from src.infrastructure.database.base import AsyncSessionLocal
+13 -13
View File
@@ -24,8 +24,8 @@ router = APIRouter(prefix="/collections", tags=["collections"])
@router.post("", response_model=CollectionResponse, status_code=status.HTTP_201_CREATED)
async def create_collection(
collection_data: CollectionCreate,
current_user: FromDishka[User] = FromDishka(),
use_cases: FromDishka[CollectionUseCases] = FromDishka()
current_user: User = FromDishka(),
use_cases: CollectionUseCases = FromDishka()
):
"""Создать коллекцию"""
collection = await use_cases.create_collection(
@@ -40,7 +40,7 @@ async def create_collection(
@router.get("/{collection_id}", response_model=CollectionResponse)
async def get_collection(
collection_id: UUID,
use_cases: FromDishka[CollectionUseCases] = FromDishka()
use_cases: CollectionUseCases = FromDishka()
):
"""Получить коллекцию по ID"""
collection = await use_cases.get_collection(collection_id)
@@ -51,8 +51,8 @@ async def get_collection(
async def update_collection(
collection_id: UUID,
collection_data: CollectionUpdate,
current_user: FromDishka[User] = FromDishka(),
use_cases: FromDishka[CollectionUseCases] = FromDishka()
current_user: User = FromDishka(),
use_cases: CollectionUseCases = FromDishka()
):
"""Обновить коллекцию"""
collection = await use_cases.update_collection(
@@ -68,8 +68,8 @@ async def update_collection(
@router.delete("/{collection_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_collection(
collection_id: UUID,
current_user: FromDishka[User] = FromDishka(),
use_cases: FromDishka[CollectionUseCases] = FromDishka()
current_user: User = FromDishka(),
use_cases: CollectionUseCases = FromDishka()
):
"""Удалить коллекцию"""
await use_cases.delete_collection(collection_id, current_user.user_id)
@@ -80,8 +80,8 @@ async def delete_collection(
async def list_collections(
skip: int = 0,
limit: int = 100,
current_user: FromDishka[User] = FromDishka(),
use_cases: FromDishka[CollectionUseCases] = FromDishka()
current_user: User = FromDishka(),
use_cases: CollectionUseCases = FromDishka()
):
"""Получить список коллекций, доступных пользователю"""
collections = await use_cases.list_user_collections(
@@ -96,8 +96,8 @@ async def list_collections(
async def grant_access(
collection_id: UUID,
access_data: CollectionAccessGrant,
current_user: FromDishka[User] = FromDishka(),
use_cases: FromDishka[CollectionUseCases] = FromDishka()
current_user: User = FromDishka(),
use_cases: CollectionUseCases = FromDishka()
):
"""Предоставить доступ пользователю к коллекции"""
access = await use_cases.grant_access(
@@ -112,8 +112,8 @@ async def grant_access(
async def revoke_access(
collection_id: UUID,
user_id: UUID,
current_user: FromDishka[User] = FromDishka(),
use_cases: FromDishka[CollectionUseCases] = FromDishka()
current_user: User = FromDishka(),
use_cases: CollectionUseCases = FromDishka()
):
"""Отозвать доступ пользователя к коллекции"""
await use_cases.revoke_access(collection_id, user_id, current_user.user_id)
@@ -21,8 +21,8 @@ router = APIRouter(prefix="/conversations", tags=["conversations"])
@router.post("", response_model=ConversationResponse, status_code=status.HTTP_201_CREATED)
async def create_conversation(
conversation_data: ConversationCreate,
current_user: FromDishka[User] = FromDishka(),
use_cases: FromDishka[ConversationUseCases] = FromDishka()
current_user: User = FromDishka(),
use_cases: ConversationUseCases = FromDishka()
):
"""Создать беседу"""
conversation = await use_cases.create_conversation(
@@ -35,8 +35,8 @@ async def create_conversation(
@router.get("/{conversation_id}", response_model=ConversationResponse)
async def get_conversation(
conversation_id: UUID,
current_user: FromDishka[User] = FromDishka(),
use_cases: FromDishka[ConversationUseCases] = FromDishka()
current_user: User = FromDishka(),
use_cases: ConversationUseCases = FromDishka()
):
"""Получить беседу по ID"""
conversation = await use_cases.get_conversation(conversation_id, current_user.user_id)
@@ -46,8 +46,8 @@ async def get_conversation(
@router.delete("/{conversation_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_conversation(
conversation_id: UUID,
current_user: FromDishka[User] = FromDishka(),
use_cases: FromDishka[ConversationUseCases] = FromDishka()
current_user: User = FromDishka(),
use_cases: ConversationUseCases = FromDishka()
):
"""Удалить беседу"""
await use_cases.delete_conversation(conversation_id, current_user.user_id)
@@ -58,8 +58,8 @@ async def delete_conversation(
async def list_conversations(
skip: int = 0,
limit: int = 100,
current_user: FromDishka[User] = FromDishka(),
use_cases: FromDishka[ConversationUseCases] = FromDishka()
current_user: User = FromDishka(),
use_cases: ConversationUseCases = FromDishka()
):
"""Получить список бесед пользователя"""
conversations = await use_cases.list_user_conversations(
+10 -10
View File
@@ -22,8 +22,8 @@ router = APIRouter(prefix="/documents", tags=["documents"])
@router.post("", response_model=DocumentResponse, status_code=status.HTTP_201_CREATED)
async def create_document(
document_data: DocumentCreate,
current_user: FromDishka[User] = FromDishka(),
use_cases: FromDishka[DocumentUseCases] = FromDishka()
current_user: User = FromDishka(),
use_cases: DocumentUseCases = FromDishka()
):
"""Создать документ"""
document = await use_cases.create_document(
@@ -39,8 +39,8 @@ async def create_document(
async def upload_document(
collection_id: UUID,
file: UploadFile = File(...),
current_user: FromDishka[User] = FromDishka(),
use_cases: FromDishka[DocumentUseCases] = FromDishka()
current_user: User = FromDishka(),
use_cases: DocumentUseCases = FromDishka()
):
"""Загрузить и распарсить PDF документ или изображение"""
if not file.filename:
@@ -70,7 +70,7 @@ async def upload_document(
@router.get("/{document_id}", response_model=DocumentResponse)
async def get_document(
document_id: UUID,
use_cases: FromDishka[DocumentUseCases] = FromDishka()
use_cases: DocumentUseCases = FromDishka()
):
"""Получить документ по ID"""
document = await use_cases.get_document(document_id)
@@ -81,8 +81,8 @@ async def get_document(
async def update_document(
document_id: UUID,
document_data: DocumentUpdate,
current_user: FromDishka[User] = FromDishka(),
use_cases: FromDishka[DocumentUseCases] = FromDishka()
current_user: User = FromDishka(),
use_cases: DocumentUseCases = FromDishka()
):
"""Обновить документ"""
document = await use_cases.update_document(
@@ -98,8 +98,8 @@ async def update_document(
@router.delete("/{document_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_document(
document_id: UUID,
current_user: FromDishka[User] = FromDishka(),
use_cases: FromDishka[DocumentUseCases] = FromDishka()
current_user: User = FromDishka(),
use_cases: DocumentUseCases = FromDishka()
):
"""Удалить документ"""
await use_cases.delete_document(document_id, current_user.user_id)
@@ -111,7 +111,7 @@ async def list_collection_documents(
collection_id: UUID,
skip: int = 0,
limit: int = 100,
use_cases: FromDishka[DocumentUseCases] = FromDishka()
use_cases: DocumentUseCases = FromDishka()
):
"""Получить документы коллекции"""
documents = await use_cases.list_collection_documents(
+7 -7
View File
@@ -22,8 +22,8 @@ router = APIRouter(prefix="/messages", tags=["messages"])
@router.post("", response_model=MessageResponse, status_code=status.HTTP_201_CREATED)
async def create_message(
message_data: MessageCreate,
current_user: FromDishka[User] = FromDishka(),
use_cases: FromDishka[MessageUseCases] = FromDishka()
current_user: User = FromDishka(),
use_cases: MessageUseCases = FromDishka()
):
"""Создать сообщение"""
message = await use_cases.create_message(
@@ -39,7 +39,7 @@ async def create_message(
@router.get("/{message_id}", response_model=MessageResponse)
async def get_message(
message_id: UUID,
use_cases: FromDishka[MessageUseCases] = FromDishka()
use_cases: MessageUseCases = FromDishka()
):
"""Получить сообщение по ID"""
message = await use_cases.get_message(message_id)
@@ -50,7 +50,7 @@ async def get_message(
async def update_message(
message_id: UUID,
message_data: MessageUpdate,
use_cases: FromDishka[MessageUseCases] = FromDishka()
use_cases: MessageUseCases = FromDishka()
):
"""Обновить сообщение"""
message = await use_cases.update_message(
@@ -64,7 +64,7 @@ async def update_message(
@router.delete("/{message_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_message(
message_id: UUID,
use_cases: FromDishka[MessageUseCases] = FromDishka()
use_cases: MessageUseCases = FromDishka()
):
"""Удалить сообщение"""
await use_cases.delete_message(message_id)
@@ -76,8 +76,8 @@ async def list_conversation_messages(
conversation_id: UUID,
skip: int = 0,
limit: int = 100,
current_user: FromDishka[User] = FromDishka(),
use_cases: FromDishka[MessageUseCases] = FromDishka()
current_user: User = FromDishka(),
use_cases: MessageUseCases = FromDishka()
):
"""Получить сообщения беседы"""
messages = await use_cases.list_conversation_messages(
+4 -4
View File
@@ -21,8 +21,8 @@ router = APIRouter(prefix="/rag", tags=["rag"])
@router.post("/index", response_model=IndexDocumentResponse, status_code=status.HTTP_200_OK)
async def index_document(
body: IndexDocumentRequest,
use_cases: FromDishka[RAGUseCases] = FromDishka(),
current_user: FromDishka[User] = FromDishka(),
use_cases: RAGUseCases = FromDishka(),
current_user: User = FromDishka(),
):
"""Индексирование идет через чанкирование, далее эмбеддинг и загрузка в векторную бд"""
result = await use_cases.index_document(body.document_id)
@@ -32,8 +32,8 @@ async def index_document(
@router.post("/question", response_model=RAGAnswer, status_code=status.HTTP_200_OK)
async def ask_question(
body: QuestionRequest,
use_cases: FromDishka[RAGUseCases] = FromDishka(),
current_user: FromDishka[User] = FromDishka(),
use_cases: RAGUseCases = FromDishka(),
current_user: User = FromDishka(),
):
"""Отвечает на вопрос, используя RAG в рамках беседы"""
result = await use_cases.ask_question(
+126 -83
View File
@@ -1,83 +1,126 @@
"""
API роутеры для работы с пользователями
"""
from __future__ import annotations
from uuid import UUID
from fastapi import APIRouter, status
from fastapi.responses import JSONResponse
from typing import List
from dishka.integrations.fastapi import FromDishka
from src.presentation.schemas.user_schemas import UserCreate, UserUpdate, UserResponse
from src.application.use_cases.user_use_cases import UserUseCases
from src.domain.entities.user import User
router = APIRouter(prefix="/users", tags=["users"])
@router.post("", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
async def create_user(
user_data: UserCreate,
use_cases: FromDishka[UserUseCases] = FromDishka()
):
"""Создать пользователя"""
user = await use_cases.create_user(
telegram_id=user_data.telegram_id,
role=user_data.role
)
return UserResponse.from_entity(user)
@router.get("/me", response_model=UserResponse)
async def get_current_user_info(
current_user: FromDishka[User] = FromDishka()
):
"""Получить информацию о текущем пользователе"""
return UserResponse.from_entity(current_user)
@router.get("/{user_id}", response_model=UserResponse)
async def get_user(
user_id: UUID,
use_cases: FromDishka[UserUseCases] = FromDishka()
):
"""Получить пользователя по ID"""
user = await use_cases.get_user(user_id)
return UserResponse.from_entity(user)
@router.put("/{user_id}", response_model=UserResponse)
async def update_user(
user_id: UUID,
user_data: UserUpdate,
use_cases: FromDishka[UserUseCases] = FromDishka()
):
"""Обновить пользователя"""
user = await use_cases.update_user(
user_id=user_id,
telegram_id=user_data.telegram_id,
role=user_data.role
)
return UserResponse.from_entity(user)
@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_user(
user_id: UUID,
use_cases: FromDishka[UserUseCases] = FromDishka()
):
"""Удалить пользователя"""
await use_cases.delete_user(user_id)
return JSONResponse(status_code=status.HTTP_204_NO_CONTENT, content=None)
@router.get("", response_model=List[UserResponse])
async def list_users(
skip: int = 0,
limit: int = 100,
use_cases: FromDishka[UserUseCases] = FromDishka()
):
"""Получить список пользователей"""
users = await use_cases.list_users(skip=skip, limit=limit)
return [UserResponse.from_entity(user) for user in users]
"""
API роутеры для работы с пользователями
"""
from __future__ import annotations
from uuid import UUID
from fastapi import APIRouter, status, Depends
from fastapi.responses import JSONResponse
from typing import List, Annotated
from dishka.integrations.fastapi import FromDishka, inject
from src.presentation.schemas.user_schemas import UserCreate, UserUpdate, UserResponse
from src.application.use_cases.user_use_cases import UserUseCases
from src.domain.entities.user import User
router = APIRouter(prefix="/users", tags=["users"])
@router.post("", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
@inject
async def create_user(
user_data: UserCreate,
use_cases: UserUseCases = FromDishka()
) -> UserResponse:
"""Создать пользователя"""
user = await use_cases.create_user(
telegram_id=user_data.telegram_id,
role=user_data.role
)
return UserResponse.from_entity(user)
@router.get("/me", response_model=UserResponse)
@inject
async def get_current_user_info(
current_user: User = FromDishka()
):
"""Получить информацию о текущем пользователе"""
return UserResponse.from_entity(current_user)
@router.get("/telegram/{telegram_id}", response_model=UserResponse)
@inject
async def get_user_by_telegram_id(
telegram_id: str,
use_cases: UserUseCases = FromDishka()
):
"""Получить пользователя по Telegram ID"""
user = await use_cases.get_user_by_telegram_id(telegram_id)
if not user:
from fastapi import HTTPException
raise HTTPException(status_code=404, detail=f"Пользователь с telegram_id {telegram_id} не найден")
return UserResponse.from_entity(user)
@router.post("/telegram/{telegram_id}/increment-questions", response_model=UserResponse)
@inject
async def increment_questions(
telegram_id: str,
use_cases: UserUseCases = FromDishka()
):
"""Увеличить счетчик использованных вопросов"""
user = await use_cases.increment_questions_used(telegram_id)
return UserResponse.from_entity(user)
@router.post("/telegram/{telegram_id}/activate-premium", response_model=UserResponse)
@inject
async def activate_premium(
telegram_id: str,
days: int = 30,
use_cases: UserUseCases = FromDishka()
):
"""Активировать premium статус"""
user = await use_cases.activate_premium(telegram_id, days=days)
return UserResponse.from_entity(user)
@router.get("/{user_id}", response_model=UserResponse)
@inject
async def get_user(
user_id: UUID,
use_cases: UserUseCases = FromDishka()
):
"""Получить пользователя по ID"""
user = await use_cases.get_user(user_id)
return UserResponse.from_entity(user)
@router.put("/{user_id}", response_model=UserResponse)
@inject
async def update_user(
user_id: UUID,
user_data: UserUpdate,
use_cases: UserUseCases = FromDishka()
):
"""Обновить пользователя"""
user = await use_cases.update_user(
user_id=user_id,
telegram_id=user_data.telegram_id,
role=user_data.role
)
return UserResponse.from_entity(user)
@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
@inject
async def delete_user(
user_id: UUID,
use_cases: UserUseCases = FromDishka()
):
"""Удалить пользователя"""
await use_cases.delete_user(user_id)
return JSONResponse(status_code=status.HTTP_204_NO_CONTENT, content=None)
@router.get("", response_model=List[UserResponse])
@inject
async def list_users(
skip: int = 0,
limit: int = 100,
use_cases: UserUseCases = FromDishka()
):
"""Получить список пользователей"""
users = await use_cases.list_users(skip=skip, limit=limit)
return [UserResponse.from_entity(user) for user in users]
+4 -2
View File
@@ -2,9 +2,11 @@ from __future__ import annotations
import sys
import os
from pathlib import Path
if '/app' not in sys.path:
sys.path.insert(0, '/app')
backend_dir = Path(__file__).parent.parent.parent
if str(backend_dir) not in sys.path:
sys.path.insert(0, str(backend_dir))
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
@@ -30,6 +30,9 @@ class UserResponse(BaseModel):
telegram_id: str
role: UserRole
created_at: datetime
is_premium: bool = False
premium_until: datetime | None = None
questions_used: int = 0
@classmethod
def from_entity(cls, user: "User") -> "UserResponse":
@@ -38,7 +41,10 @@ class UserResponse(BaseModel):
user_id=user.user_id,
telegram_id=user.telegram_id,
role=user.role,
created_at=user.created_at
created_at=user.created_at,
is_premium=user.is_premium,
premium_until=user.premium_until,
questions_used=user.questions_used
)
class Config:
+3 -4
View File
@@ -1,7 +1,7 @@
from dishka import Container, Provider, Scope, provide
from fastapi import Request
from sqlalchemy.ext.asyncio import AsyncSession
from contextlib import asynccontextmanager
from collections.abc import AsyncIterator
from src.infrastructure.database.base import AsyncSessionLocal
from src.infrastructure.repositories.postgresql.user_repository import PostgreSQLUserRepository
@@ -39,8 +39,7 @@ from src.application.use_cases.rag_use_cases import RAGUseCases
class DatabaseProvider(Provider):
@provide(scope=Scope.REQUEST)
@asynccontextmanager
async def get_db(self) -> AsyncSession:
async def get_db(self) -> AsyncIterator[AsyncSession]:
async with AsyncSessionLocal() as session:
try:
yield session
@@ -77,7 +76,7 @@ class RepositoryProvider(Provider):
class ServiceProvider(Provider):
@provide(scope=Scope.APP)
def get_redis_client(self) -> RedisClient:
return RedisClient()
return RedisClient(host=settings.REDIS_HOST, port=settings.REDIS_PORT)
@provide(scope=Scope.APP)
def get_cache_service(self, redis_client: RedisClient) -> CacheService: