"""SQLite implementation of UserRepository.""" from datetime import datetime from uuid import UUID import aiosqlite from app.db.connection import commit from app.entities.user import User from app.repositories.filter_validator import validate_filters from app.repositories.user_repository import UserRepository from app.utils.errors import NotFoundError class SQLiteUserRepository(UserRepository): def __init__(self, connection: aiosqlite.Connection) -> None: self._conn = connection @staticmethod def _row_to_entity(row: aiosqlite.Row) -> User: return User( id=UUID(row["id"]), personal_data_uuid=UUID(row["personal_data_uuid"]), email=row["email"], hashed_password=row["hashed_password"], created_at=datetime.fromisoformat(row["created_at"]), is_active=bool(row["is_active"]), ) async def create(self, item: User) -> User: now = datetime.now() item.created_at = item.created_at or now await self._conn.execute( """ INSERT INTO "user" (id, personal_data_uuid, email, hashed_password, created_at, is_active) VALUES (?, ?, ?, ?, ?, ?) """, ( str(item.id), str(item.personal_data_uuid), item.email, item.hashed_password, item.created_at.isoformat(), int(item.is_active), ), ) await commit(self._conn) return item async def get_by_id(self, id: UUID) -> User | None: cursor = await self._conn.execute('SELECT * FROM "user" WHERE id = ?', (str(id),)) row = await cursor.fetchone() return self._row_to_entity(row) if row else None async def get_by_email(self, email: str) -> User | None: cursor = await self._conn.execute('SELECT * FROM "user" WHERE email = ?', (email,)) row = await cursor.fetchone() return self._row_to_entity(row) if row else None async def get_by_personal_data_uuid(self, personal_data_uuid: UUID) -> User | None: cursor = await self._conn.execute( 'SELECT * FROM "user" WHERE personal_data_uuid = ?', (str(personal_data_uuid),) ) row = await cursor.fetchone() return self._row_to_entity(row) if row else None async def update(self, id: UUID, item: User) -> User: # `personal_data_uuid` is included here (unlike a typical immutable # FK) because `WorkspaceService.redeem_invitation` and # `AuthService.register(..., invite_token=...)` re-point a user's # login onto an existing placeholder `personal_data` row when # claiming it. `user.personal_data_uuid` is UNIQUE, so re-pointing it # onto a row already linked to another user fails at the DB. cursor = await self._conn.execute( """ UPDATE "user" SET personal_data_uuid = ?, email = ?, hashed_password = ?, is_active = ? WHERE id = ? """, ( str(item.personal_data_uuid), item.email, item.hashed_password, int(item.is_active), str(id), ), ) await commit(self._conn) if cursor.rowcount == 0: raise NotFoundError(f"User {id} not found") refreshed = await self.get_by_id(id) assert refreshed is not None return refreshed async def delete(self, id: UUID) -> None: cursor = await self._conn.execute('DELETE FROM "user" WHERE id = ?', (str(id),)) await commit(self._conn) if cursor.rowcount == 0: raise NotFoundError(f"User {id} not found") async def list(self, **filters: object) -> list[User]: validate_filters(filters, set()) cursor = await self._conn.execute('SELECT * FROM "user"') rows = await cursor.fetchall() return [self._row_to_entity(r) for r in rows]