"""SQL implementation of the LikesService protocol. Extracted from likes/bp/data/routes.py and likes/bp/actions/routes.py to enable sx defquery/defaction conversion. """ from __future__ import annotations from sqlalchemy import select, update, func from sqlalchemy.ext.asyncio import AsyncSession def _Like(): from models.like import Like return Like class SqlLikesService: async def is_liked( self, session: AsyncSession, *, user_id: int, target_type: str, target_slug: str | None = None, target_id: int | None = None, ) -> bool: Like = _Like() if not user_id or not target_type: return False filters = [ Like.user_id == user_id, Like.target_type == target_type, Like.deleted_at.is_(None), ] if target_slug is not None: filters.append(Like.target_slug == target_slug) elif target_id is not None: filters.append(Like.target_id == target_id) else: return False row = await session.scalar(select(Like.id).where(*filters)) return row is not None async def liked_slugs( self, session: AsyncSession, *, user_id: int, target_type: str, ) -> list[str]: Like = _Like() if not user_id or not target_type: return [] result = await session.execute( select(Like.target_slug).where( Like.user_id == user_id, Like.target_type == target_type, Like.target_slug.isnot(None), Like.deleted_at.is_(None), ) ) return list(result.scalars().all()) async def liked_ids( self, session: AsyncSession, *, user_id: int, target_type: str, ) -> list[int]: Like = _Like() if not user_id or not target_type: return [] result = await session.execute( select(Like.target_id).where( Like.user_id == user_id, Like.target_type == target_type, Like.target_id.isnot(None), Like.deleted_at.is_(None), ) ) return list(result.scalars().all()) async def toggle( self, session: AsyncSession, *, user_id: int, target_type: str, target_slug: str | None = None, target_id: int | None = None, ) -> bool: """Toggle a like. Returns True if now liked, False if unliked.""" Like = _Like() filters = [ Like.user_id == user_id, Like.target_type == target_type, Like.deleted_at.is_(None), ] if target_slug is not None: filters.append(Like.target_slug == target_slug) elif target_id is not None: filters.append(Like.target_id == target_id) else: raise ValueError("target_slug or target_id required") existing = await session.scalar(select(Like).where(*filters)) if existing: await session.execute( update(Like).where(Like.id == existing.id).values(deleted_at=func.now()) ) return False else: new_like = Like( user_id=user_id, target_type=target_type, target_slug=target_slug, target_id=target_id, ) session.add(new_like) await session.flush() return True