"""Refresh-token repository — adapter implementing ``app.domain.ports.RefreshTokenRepository``. """ import datetime as dt import uuid from sqlalchemy import select, update from sqlalchemy.ext.asyncio import AsyncSession from app.infrastructure.db.models import RefreshTokenModel class SqlAlchemyRefreshTokenRepository: def __init__(self, session: AsyncSession) -> None: self._session = session async def add( self, *, jti: uuid.UUID, user_id: uuid.UUID, token_hash: str, expires_at: dt.datetime, ) -> None: self._session.add( RefreshTokenModel( jti=jti, user_id=user_id, token_hash=token_hash, expires_at=expires_at, ) ) await self._session.flush() async def is_valid(self, jti: uuid.UUID) -> bool: row = ( await self._session.execute( select(RefreshTokenModel).where(RefreshTokenModel.jti == jti) ) ).scalar_one_or_none() if row is None or row.revoked_at is not None: return False return row.expires_at > dt.datetime.now(dt.UTC) async def revoke(self, jti: uuid.UUID) -> None: await self._session.execute( update(RefreshTokenModel) .where(RefreshTokenModel.jti == jti, RefreshTokenModel.revoked_at.is_(None)) .values(revoked_at=dt.datetime.now(dt.UTC)) ) async def revoke_all_for_user(self, user_id: uuid.UUID) -> None: await self._session.execute( update(RefreshTokenModel) .where( RefreshTokenModel.user_id == user_id, RefreshTokenModel.revoked_at.is_(None), ) .values(revoked_at=dt.datetime.now(dt.UTC)) )