From 880e3c8fec5cce522a0f8176eed41c9aec9362ef Mon Sep 17 00:00:00 2001 From: itqop Date: Sat, 18 Oct 2025 15:25:28 +0300 Subject: [PATCH] add users --- src/hubgw/api/deps.py | 6 +++++ src/hubgw/api/v1/users.py | 24 +++++++++++++++++++ src/hubgw/models/users.py | 36 ++++++++++++++++++++++++++++ src/hubgw/repositories/users_repo.py | 20 ++++++++++++++++ src/hubgw/schemas/users.py | 10 ++++++++ src/hubgw/services/users_service.py | 21 ++++++++++++++++ test.py | 0 7 files changed, 117 insertions(+) create mode 100644 src/hubgw/api/v1/users.py create mode 100644 src/hubgw/models/users.py create mode 100644 src/hubgw/repositories/users_repo.py create mode 100644 src/hubgw/schemas/users.py create mode 100644 src/hubgw/services/users_service.py delete mode 100644 test.py diff --git a/src/hubgw/api/deps.py b/src/hubgw/api/deps.py index 5770eb5..766e8bc 100644 --- a/src/hubgw/api/deps.py +++ b/src/hubgw/api/deps.py @@ -9,6 +9,7 @@ from hubgw.context import AppContext from hubgw.services.homes_service import HomesService from hubgw.services.kits_service import KitsService from hubgw.services.cooldowns_service import CooldownsService +from hubgw.services.users_service import UserService from hubgw.services.warps_service import WarpsService from hubgw.services.whitelist_service import WhitelistService from hubgw.services.punishments_service import PunishmentsService @@ -83,3 +84,8 @@ def get_luckperms_service(session: Annotated[AsyncSession, Depends(get_session)] def get_teleport_history_service(session: Annotated[AsyncSession, Depends(get_session)]) -> TeleportHistoryService: """Get teleport history service.""" return TeleportHistoryService(session) + + +def get_user_service(session: Annotated[AsyncSession, Depends(get_session)]) -> UserService: + """Get user service.""" + return UserService(session) diff --git a/src/hubgw/api/v1/users.py b/src/hubgw/api/v1/users.py new file mode 100644 index 0000000..7deac24 --- /dev/null +++ b/src/hubgw/api/v1/users.py @@ -0,0 +1,24 @@ +"""User endpoints.""" + +from fastapi import APIRouter, Depends +from typing import Annotated + +from hubgw.api.deps import get_user_service, verify_api_key +from hubgw.services.users_service import UserService +from hubgw.schemas.users import GetUserGameIdResponse +from hubgw.core.errors import AppError, create_http_exception + +router = APIRouter() + + +@router.get("/users/{name}/game-id", response_model=GetUserGameIdResponse) +async def get_user_game_id( + name: str, + service: Annotated[UserService, Depends(get_user_service)], + _: Annotated[str, Depends(verify_api_key)] +): + """Get game ID by user name.""" + try: + return await service.get_game_id_by_name(name) + except AppError as e: + raise create_http_exception(e) diff --git a/src/hubgw/models/users.py b/src/hubgw/models/users.py new file mode 100644 index 0000000..7a008ec --- /dev/null +++ b/src/hubgw/models/users.py @@ -0,0 +1,36 @@ +"""User model.""" + +from sqlalchemy import Column, Integer, String, DateTime, Boolean, Numeric, ForeignKey, Index +from sqlalchemy.dialects.postgresql import UUID +from hubgw.models.base import Base + + +class User(Base): + """User model.""" + + __tablename__ = "users" + + id = Column(Integer, primary_key=True, autoincrement=True) + name = Column(String(191), nullable=False) + email = Column(String(191), unique=True, nullable=True) + email_verified_at = Column(DateTime(timezone=True), nullable=True) + password = Column(String(191), nullable=False) + role_id = Column(Integer, ForeignKey("roles.id"), nullable=False) + money = Column(Numeric(14, 2), default=0) + game_id = Column(String(191), nullable=True) + avatar = Column(String, nullable=True) # TEXT поле + access_token = Column(String(191), nullable=True) + two_factor_secret = Column(String(191), nullable=True) + two_factor_recovery_codes = Column(String(191), nullable=True) + last_login_ip = Column(String(45), nullable=True) + last_login_at = Column(DateTime(timezone=True), nullable=True) + is_banned = Column(Boolean, default=False) + remember_token = Column(String(100), nullable=True) + created_at = Column(DateTime(timezone=True), nullable=True) + updated_at = Column(DateTime(timezone=True), nullable=True) + deleted_at = Column(DateTime(timezone=True), nullable=True) + password_changed_at = Column(DateTime(timezone=True), nullable=True) + + __table_args__ = ( + Index('idx_users_email', 'email', unique=True), + ) \ No newline at end of file diff --git a/src/hubgw/repositories/users_repo.py b/src/hubgw/repositories/users_repo.py new file mode 100644 index 0000000..04876a8 --- /dev/null +++ b/src/hubgw/repositories/users_repo.py @@ -0,0 +1,20 @@ +"""User repository.""" + +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select +from typing import Optional + +from hubgw.models.user import User + + +class UserRepository: + """User repository for database operations.""" + + def __init__(self, session: AsyncSession): + self.session = session + + async def get_game_id_by_name(self, name: str) -> Optional[str]: + """Get game_id by user name.""" + stmt = select(User.game_id).where(User.name == name) + result = await self.session.execute(stmt) + return result.scalar_one_or_none() diff --git a/src/hubgw/schemas/users.py b/src/hubgw/schemas/users.py new file mode 100644 index 0000000..d8cc501 --- /dev/null +++ b/src/hubgw/schemas/users.py @@ -0,0 +1,10 @@ +"""User schemas.""" + +from pydantic import BaseModel +from typing import Optional + + +class GetUserGameIdResponse(BaseModel): + """Response schema for getting user's game ID.""" + + game_id: Optional[str] diff --git a/src/hubgw/services/users_service.py b/src/hubgw/services/users_service.py new file mode 100644 index 0000000..553c8c5 --- /dev/null +++ b/src/hubgw/services/users_service.py @@ -0,0 +1,21 @@ +"""User service.""" + +from sqlalchemy.ext.asyncio import AsyncSession + +from hubgw.repositories.user_repo import UserRepository +from hubgw.schemas.user import GetUserGameIdResponse +from hubgw.core.errors import NotFoundError + + +class UserService: + """User service for business logic.""" + + def __init__(self, session: AsyncSession): + self.repo = UserRepository(session) + + async def get_game_id_by_name(self, name: str) -> GetUserGameIdResponse: + """Get game_id by user name.""" + game_id = await self.repo.get_game_id_by_name(name) + if game_id is None: + raise NotFoundError(f"User with name '{name}' not found or has no game_id") + return GetUserGameIdResponse(game_id=game_id) diff --git a/test.py b/test.py deleted file mode 100644 index e69de29..0000000