Files
social-app/backend/src/v1/users/repository.py
T

105 lines
3.4 KiB
Python
Raw Normal View History

from __future__ import annotations
from typing import TYPE_CHECKING, Protocol
from uuid import UUID
from sqlalchemy import select, or_
from sqlalchemy.exc import SQLAlchemyError
from core.db.base_repository import BaseRepository
from core.logging import get_logger
from models.profile import Profile
if TYPE_CHECKING:
from sqlalchemy.ext.asyncio import AsyncSession
logger = get_logger("v1.users.repository")
class UserRepository(Protocol):
"""Protocol defining the user repository interface."""
async def get_by_user_id(self, user_id: UUID) -> Profile | None:
"""Get user by user ID."""
...
async def get_by_username(self, username: str) -> Profile | None:
"""Get user by username."""
...
async def update_by_user_id(
self, user_id: UUID, update_data: dict[str, str | None]
) -> Profile | None:
"""Update user by user ID. Returns updated user or None if not found."""
...
async def search_users(self, query: str, limit: int = 20) -> list[Profile]:
"""Search users by username (ilike) or email (exact match)."""
...
class SQLAlchemyUserRepository(BaseRepository[Profile]):
"""SQLAlchemy implementation of UserRepository.
Note: This repository only performs CRUD operations.
- No commit (only flush) - service layer handles transactions
- No auth logic - service layer handles authorization
- No HTTP exceptions - returns None or raises SQLAlchemyError
"""
def __init__(self, session: AsyncSession) -> None:
super().__init__(session, Profile)
async def get_by_user_id(self, user_id: UUID) -> Profile | None:
try:
return await self.get_by_id(user_id)
except SQLAlchemyError:
logger.exception("User lookup failed", user_id=str(user_id))
raise
async def get_by_username(self, username: str) -> Profile | None:
try:
stmt = (
select(Profile)
.where(Profile.username == username)
.where(Profile.deleted_at.is_(None))
.order_by(Profile.created_at.asc())
.limit(1)
)
result = await self._session.execute(stmt)
return result.scalar_one_or_none()
except SQLAlchemyError:
logger.exception("User lookup failed", username=username)
raise
async def update_by_user_id(
self, user_id: UUID, update_data: dict[str, str | None]
) -> Profile | None:
if not update_data:
return await self.get_by_user_id(user_id)
try:
return await self.update_by_id(user_id, update_data)
except SQLAlchemyError:
logger.exception("User update failed", user=str(user_id))
raise
async def search_users(self, query: str, limit: int = 20) -> list[Profile]:
try:
stmt = (
select(Profile)
.where(Profile.deleted_at.is_(None))
.where(
or_(
Profile.username.ilike(f"%{query}%"),
)
)
.order_by(Profile.created_at.asc())
.limit(limit)
)
result = await self._session.execute(stmt)
return list(result.scalars().all())
except SQLAlchemyError:
logger.exception("User search failed", query=query)
raise