From e7ed89e04e1fdf6bcfe269f46c79a546215c77db Mon Sep 17 00:00:00 2001 From: Brian Bjarke Jensen Date: Tue, 11 Nov 2025 18:52:19 +0100 Subject: [PATCH] implemented postgres support in repositories --- src/baby_monitor/models/db/sleep.py | 2 +- .../repositories/child/sqlite_child.py | 14 ++-- .../repositories/child_invitation/__init__.py | 10 ++- .../sqlite_child_invitation.py | 41 ++++++++++-- .../get_child_invitation_repository.py | 11 ++-- .../dependencies/get_child_repository.py | 11 ++-- .../repositories/dependencies/get_database.py | 2 +- .../get_diaper_change_repository.py | 11 ++-- .../dependencies/get_feeding_repository.py | 11 ++-- .../dependencies/get_invitation_repository.py | 11 ++-- .../dependencies/get_sleep_repository.py | 11 ++-- .../dependencies/get_user_repository.py | 6 +- .../diaper_change/sqlite_diaper_change.py | 14 ++-- .../repositories/feeding/sqlite_feeding.py | 14 ++-- .../repositories/interfaces/__init__.py | 2 +- ...ation.py => child_invitation_interface.py} | 0 .../repositories/invitation/__init__.py | 7 +- .../invitation/sqlite_invitation.py | 66 +++++++++++++++---- .../repositories/sleep/sqlite_sleep.py | 14 ++-- .../repositories/user/postgresql_user.py | 64 ------------------ .../repositories/user/sqlite_user.py | 12 +++- src/baby_monitor/routers/admin.py | 12 +--- src/baby_monitor/routers/auth.py | 4 +- src/baby_monitor/routers/child.py | 44 ++++++++----- src/baby_monitor/routers/diaper_change.py | 38 ++++++----- src/baby_monitor/routers/feeding.py | 61 +++++++++++------ src/baby_monitor/routers/sleep.py | 56 +++++++++++----- src/baby_monitor/utils/__init__.py | 10 +++ src/baby_monitor/utils/hash_password.py | 17 +++++ ...cess_control.py => verify_child_access.py} | 8 ++- .../utils/{password.py => verify_password.py} | 16 +---- 31 files changed, 364 insertions(+), 236 deletions(-) rename src/baby_monitor/repositories/interfaces/{child_invitation.py => child_invitation_interface.py} (100%) delete mode 100644 src/baby_monitor/repositories/user/postgresql_user.py create mode 100644 src/baby_monitor/utils/__init__.py create mode 100644 src/baby_monitor/utils/hash_password.py rename src/baby_monitor/utils/{access_control.py => verify_child_access.py} (75%) rename src/baby_monitor/utils/{password.py => verify_password.py} (54%) diff --git a/src/baby_monitor/models/db/sleep.py b/src/baby_monitor/models/db/sleep.py index 96a9bda..fb43199 100644 --- a/src/baby_monitor/models/db/sleep.py +++ b/src/baby_monitor/models/db/sleep.py @@ -1,8 +1,8 @@ """Sleep database model.""" from typing import TYPE_CHECKING -from sqlalchemy import Column, Integer, DateTime, ForeignKey from datetime import datetime, UTC +from sqlalchemy import Column, Integer, DateTime, ForeignKey if TYPE_CHECKING: from sqlalchemy.orm import DeclarativeBase diff --git a/src/baby_monitor/repositories/child/sqlite_child.py b/src/baby_monitor/repositories/child/sqlite_child.py index 4c4c3b8..354d27f 100644 --- a/src/baby_monitor/repositories/child/sqlite_child.py +++ b/src/baby_monitor/repositories/child/sqlite_child.py @@ -1,17 +1,23 @@ -"""SQLite implementation of child repository.""" +"""Database-agnostic implementation of child repository. + +Works with both SQLite and PostgreSQL. +""" from datetime import datetime, UTC from sqlalchemy.orm import Session -from baby_monitor.repositories.interfaces.child_repository_interface import ( +from baby_monitor.repositories.interfaces import ( ChildRepositoryInterface, ) from baby_monitor.models.db.child import Child from baby_monitor.models.db.child_parent import ChildParent -class SQLiteChildRepository(ChildRepositoryInterface): - """SQLite implementation for child data access.""" +class DatabaseChildRepository(ChildRepositoryInterface): + """Database implementation for child data access. + + Works with both SQLite and PostgreSQL databases. + """ def __init__(self, db: Session): self.db = db diff --git a/src/baby_monitor/repositories/child_invitation/__init__.py b/src/baby_monitor/repositories/child_invitation/__init__.py index 0ef7a5f..49b26ac 100644 --- a/src/baby_monitor/repositories/child_invitation/__init__.py +++ b/src/baby_monitor/repositories/child_invitation/__init__.py @@ -1,7 +1,13 @@ """Child invitation repository implementations.""" from baby_monitor.repositories.child_invitation.sqlite_child_invitation import ( # noqa: E501 - SQLiteChildInvitationRepository, + DatabaseChildInvitationRepository, ) -__all__ = ["SQLiteChildInvitationRepository"] +# Keep old name for backward compatibility +SQLiteChildInvitationRepository = DatabaseChildInvitationRepository + +__all__ = [ + "DatabaseChildInvitationRepository", + "SQLiteChildInvitationRepository", +] diff --git a/src/baby_monitor/repositories/child_invitation/sqlite_child_invitation.py b/src/baby_monitor/repositories/child_invitation/sqlite_child_invitation.py index 0371999..b8ac2e9 100644 --- a/src/baby_monitor/repositories/child_invitation/sqlite_child_invitation.py +++ b/src/baby_monitor/repositories/child_invitation/sqlite_child_invitation.py @@ -1,20 +1,45 @@ -"""SQLite implementation of child invitation repository.""" +"""Database-agnostic implementation of child invitation repository. + +Supports both SQLite and PostgreSQL: +- SQLite: stores datetimes as naive UTC +- PostgreSQL: stores datetimes as timezone-aware +""" from datetime import datetime, UTC from sqlalchemy.orm import Session from baby_monitor.models.child_invitation import ChildInvitation -from baby_monitor.repositories.interfaces.child_invitation import ( +from baby_monitor.repositories.interfaces import ( ChildInvitationRepositoryInterface, ) -class SQLiteChildInvitationRepository(ChildInvitationRepositoryInterface): - """SQLite implementation of child invitation repository.""" +class DatabaseChildInvitationRepository(ChildInvitationRepositoryInterface): + """Database implementation of child invitation repository. + + Works with both SQLite and PostgreSQL databases. + """ def __init__(self, db: Session): """Initialize the repository with a database session.""" self.db = db + def _get_now_utc(self, reference_dt: datetime) -> datetime: + """ + Get current UTC time matching the timezone awareness of reference. + + Args: + reference_dt: A datetime from DB to match timezone format + + Returns: + Current UTC time (naive for SQLite, aware for PostgreSQL) + """ + now = datetime.now(UTC) + if reference_dt.tzinfo is None: + # SQLite: return naive UTC + return now.replace(tzinfo=None) + # PostgreSQL: return timezone-aware + return now + def create_invitation( self, code: str, @@ -59,7 +84,9 @@ class SQLiteChildInvitationRepository(ChildInvitationRepositoryInterface): def verify_invitation(self, code: str) -> bool: """Verify if an invitation code is valid and not expired.""" invitation = ( - self.db.query(ChildInvitation).filter(ChildInvitation.code == code).first() + self.db.query(ChildInvitation) + .filter(ChildInvitation.code == code) + .first() ) if not invitation: @@ -68,8 +95,8 @@ class SQLiteChildInvitationRepository(ChildInvitationRepositoryInterface): if invitation.is_consumed: return False - # Compare as naive UTC datetimes (SQLite stores without timezone) - now_utc = datetime.now(UTC).replace(tzinfo=None) + # Get current time matching database timezone format + now_utc = self._get_now_utc(invitation.expires_at) if invitation.expires_at < now_utc: return False diff --git a/src/baby_monitor/repositories/dependencies/get_child_invitation_repository.py b/src/baby_monitor/repositories/dependencies/get_child_invitation_repository.py index 76ffad7..d5c73f7 100644 --- a/src/baby_monitor/repositories/dependencies/get_child_invitation_repository.py +++ b/src/baby_monitor/repositories/dependencies/get_child_invitation_repository.py @@ -4,13 +4,16 @@ from typing import Annotated from fastapi import Depends from sqlalchemy.orm import Session from baby_monitor.repositories.child_invitation.sqlite_child_invitation import ( # noqa: E501 - SQLiteChildInvitationRepository, + DatabaseChildInvitationRepository, +) +from baby_monitor.repositories.interfaces import ( + ChildInvitationRepositoryInterface, ) from baby_monitor.repositories.dependencies.get_database import get_database def get_child_invitation_repository( db: Annotated[Session, Depends(get_database)], -) -> SQLiteChildInvitationRepository: - """Get child invitation repository instance.""" - return SQLiteChildInvitationRepository(db) +) -> ChildInvitationRepositoryInterface: + """Get child invitation repository instance (works with any database).""" + return DatabaseChildInvitationRepository(db) diff --git a/src/baby_monitor/repositories/dependencies/get_child_repository.py b/src/baby_monitor/repositories/dependencies/get_child_repository.py index a5d677b..27cd71a 100644 --- a/src/baby_monitor/repositories/dependencies/get_child_repository.py +++ b/src/baby_monitor/repositories/dependencies/get_child_repository.py @@ -5,11 +5,14 @@ from fastapi import Depends from sqlalchemy.orm import Session from baby_monitor.repositories.dependencies.get_database import get_database -from baby_monitor.repositories.child.sqlite_child import SQLiteChildRepository +from baby_monitor.repositories.child.sqlite_child import DatabaseChildRepository +from baby_monitor.repositories.interfaces.child_repository_interface import ( + ChildRepositoryInterface, +) def get_child_repository( db: Annotated[Session, Depends(get_database)], -) -> SQLiteChildRepository: - """Get child repository instance.""" - return SQLiteChildRepository(db) +) -> ChildRepositoryInterface: + """Get child repository instance (works with any database).""" + return DatabaseChildRepository(db) diff --git a/src/baby_monitor/repositories/dependencies/get_database.py b/src/baby_monitor/repositories/dependencies/get_database.py index 414486a..e45d29b 100644 --- a/src/baby_monitor/repositories/dependencies/get_database.py +++ b/src/baby_monitor/repositories/dependencies/get_database.py @@ -45,7 +45,7 @@ Base: "type[DeclarativeBase]" = declarative_base() def _ensure_admin_user() -> None: """Create or update admin user from environment variables.""" from baby_monitor.models.db.user import User - from baby_monitor.utils.password import hash_password + from baby_monitor.utils import hash_password admin_username = os.getenv("ADMIN_USERNAME") admin_password = os.getenv("ADMIN_PASSWORD") diff --git a/src/baby_monitor/repositories/dependencies/get_diaper_change_repository.py b/src/baby_monitor/repositories/dependencies/get_diaper_change_repository.py index 5616377..3baf122 100644 --- a/src/baby_monitor/repositories/dependencies/get_diaper_change_repository.py +++ b/src/baby_monitor/repositories/dependencies/get_diaper_change_repository.py @@ -6,12 +6,15 @@ from sqlalchemy.orm import Session from baby_monitor.repositories.dependencies.get_database import get_database from baby_monitor.repositories.diaper_change.sqlite_diaper_change import ( - SQLiteDiaperChangeRepository, + DatabaseDiaperChangeRepository, +) +from baby_monitor.repositories.interfaces.diaper_change_repository_interface import ( # noqa: E501 + DiaperChangeRepositoryInterface, ) def get_diaper_change_repository( db: Annotated[Session, Depends(get_database)], -) -> SQLiteDiaperChangeRepository: - """Get diaper change repository instance.""" - return SQLiteDiaperChangeRepository(db) +) -> DiaperChangeRepositoryInterface: + """Get diaper change repository instance (works with any database).""" + return DatabaseDiaperChangeRepository(db) diff --git a/src/baby_monitor/repositories/dependencies/get_feeding_repository.py b/src/baby_monitor/repositories/dependencies/get_feeding_repository.py index 6f5cdb4..9a6c274 100644 --- a/src/baby_monitor/repositories/dependencies/get_feeding_repository.py +++ b/src/baby_monitor/repositories/dependencies/get_feeding_repository.py @@ -6,12 +6,15 @@ from sqlalchemy.orm import Session from baby_monitor.repositories.dependencies.get_database import get_database from baby_monitor.repositories.feeding.sqlite_feeding import ( - SQLiteFeedingRepository, + DatabaseFeedingRepository, +) +from baby_monitor.repositories.interfaces.feeding_repository_interface import ( + FeedingRepositoryInterface, ) def get_feeding_repository( db: Annotated[Session, Depends(get_database)], -) -> SQLiteFeedingRepository: - """Get feeding repository instance.""" - return SQLiteFeedingRepository(db) +) -> FeedingRepositoryInterface: + """Get feeding repository instance (works with any database).""" + return DatabaseFeedingRepository(db) diff --git a/src/baby_monitor/repositories/dependencies/get_invitation_repository.py b/src/baby_monitor/repositories/dependencies/get_invitation_repository.py index fce80f8..7528e6e 100644 --- a/src/baby_monitor/repositories/dependencies/get_invitation_repository.py +++ b/src/baby_monitor/repositories/dependencies/get_invitation_repository.py @@ -6,13 +6,16 @@ from sqlalchemy.orm import Session from baby_monitor.repositories.dependencies.get_database import get_database from baby_monitor.repositories.invitation.sqlite_invitation import ( - SQLiteInvitationRepository, + DatabaseInvitationRepository, +) +from baby_monitor.repositories.interfaces.invitation_repository_interface import ( + InvitationRepositoryInterface, ) def get_invitation_repository( db: Annotated[Session, Depends(get_database)], -) -> SQLiteInvitationRepository: +) -> InvitationRepositoryInterface: """ Provide an invitation repository instance. @@ -20,6 +23,6 @@ def get_invitation_repository( db: Database session from dependency injection Returns: - SQLiteInvitationRepository instance + InvitationRepositoryInterface instance (works with any database) """ - return SQLiteInvitationRepository(db) + return DatabaseInvitationRepository(db) diff --git a/src/baby_monitor/repositories/dependencies/get_sleep_repository.py b/src/baby_monitor/repositories/dependencies/get_sleep_repository.py index b3f5bea..133d89c 100644 --- a/src/baby_monitor/repositories/dependencies/get_sleep_repository.py +++ b/src/baby_monitor/repositories/dependencies/get_sleep_repository.py @@ -5,11 +5,14 @@ from fastapi import Depends from sqlalchemy.orm import Session from baby_monitor.repositories.dependencies.get_database import get_database -from baby_monitor.repositories.sleep.sqlite_sleep import SQLiteSleepRepository +from baby_monitor.repositories.sleep.sqlite_sleep import DatabaseSleepRepository +from baby_monitor.repositories.interfaces.sleep_repository_interface import ( + SleepRepositoryInterface, +) def get_sleep_repository( db: Annotated[Session, Depends(get_database)], -) -> SQLiteSleepRepository: - """Get sleep repository instance.""" - return SQLiteSleepRepository(db) +) -> SleepRepositoryInterface: + """Get sleep repository instance (works with any database).""" + return DatabaseSleepRepository(db) diff --git a/src/baby_monitor/repositories/dependencies/get_user_repository.py b/src/baby_monitor/repositories/dependencies/get_user_repository.py index 8d6fda4..c66685f 100644 --- a/src/baby_monitor/repositories/dependencies/get_user_repository.py +++ b/src/baby_monitor/repositories/dependencies/get_user_repository.py @@ -11,7 +11,7 @@ from baby_monitor.repositories.interfaces import ( UserRepositoryInterface, ) from baby_monitor.repositories.user.sqlite_user import ( - SQLiteUserRepository, + DatabaseUserRepository, ) # Singleton instance @@ -22,12 +22,12 @@ def get_user_repository( db: Annotated[Session, Depends(get_database)], ) -> UserRepositoryInterface: """ - Get user repository instance. + Get user repository instance (works with any database). """ global _user_repository if _user_repository is None: - _user_repository = SQLiteUserRepository(db) + _user_repository = DatabaseUserRepository(db) return _user_repository diff --git a/src/baby_monitor/repositories/diaper_change/sqlite_diaper_change.py b/src/baby_monitor/repositories/diaper_change/sqlite_diaper_change.py index f883c8e..054bd18 100644 --- a/src/baby_monitor/repositories/diaper_change/sqlite_diaper_change.py +++ b/src/baby_monitor/repositories/diaper_change/sqlite_diaper_change.py @@ -1,9 +1,12 @@ -"""SQLite implementation of diaper change repository.""" +"""Database-agnostic implementation of diaper change repository. + +Works with both SQLite and PostgreSQL. +""" from datetime import datetime from sqlalchemy.orm import Session -from baby_monitor.repositories.interfaces.diaper_change_repository_interface import ( # noqa: E501 +from baby_monitor.repositories.interfaces import ( # noqa: E501 DiaperChangeRepositoryInterface, ) from baby_monitor.models.db.diaper_change import DiaperChange @@ -11,8 +14,11 @@ from baby_monitor.models.db.child import Child from baby_monitor.models.db.child_parent import ChildParent -class SQLiteDiaperChangeRepository(DiaperChangeRepositoryInterface): - """SQLite implementation for diaper change log data access.""" +class DatabaseDiaperChangeRepository(DiaperChangeRepositoryInterface): + """Database implementation for diaper change log data access. + + Works with both SQLite and PostgreSQL databases. + """ def __init__(self, db: Session): self.db = db diff --git a/src/baby_monitor/repositories/feeding/sqlite_feeding.py b/src/baby_monitor/repositories/feeding/sqlite_feeding.py index e7a96a9..b1e5168 100644 --- a/src/baby_monitor/repositories/feeding/sqlite_feeding.py +++ b/src/baby_monitor/repositories/feeding/sqlite_feeding.py @@ -1,9 +1,12 @@ -"""SQLite implementation of feeding repository.""" +"""Database-agnostic implementation of feeding repository. + +Works with both SQLite and PostgreSQL. +""" from datetime import datetime from sqlalchemy.orm import Session -from baby_monitor.repositories.interfaces.feeding_repository_interface import ( +from baby_monitor.repositories.interfaces import ( FeedingRepositoryInterface, ) from baby_monitor.models.db.feeding import Feeding @@ -11,8 +14,11 @@ from baby_monitor.models.db.child import Child from baby_monitor.models.db.child_parent import ChildParent -class SQLiteFeedingRepository(FeedingRepositoryInterface): - """SQLite implementation for feeding log data access.""" +class DatabaseFeedingRepository(FeedingRepositoryInterface): + """Database implementation for feeding log data access. + + Works with both SQLite and PostgreSQL databases. + """ def __init__(self, db: Session): self.db = db diff --git a/src/baby_monitor/repositories/interfaces/__init__.py b/src/baby_monitor/repositories/interfaces/__init__.py index dd09ce8..72c9186 100644 --- a/src/baby_monitor/repositories/interfaces/__init__.py +++ b/src/baby_monitor/repositories/interfaces/__init__.py @@ -1,6 +1,6 @@ """Repository interfaces package.""" -from .child_invitation import ( +from .child_invitation_interface import ( ChildInvitationRepositoryInterface, ) from .credentials_repository_interface import ( diff --git a/src/baby_monitor/repositories/interfaces/child_invitation.py b/src/baby_monitor/repositories/interfaces/child_invitation_interface.py similarity index 100% rename from src/baby_monitor/repositories/interfaces/child_invitation.py rename to src/baby_monitor/repositories/interfaces/child_invitation_interface.py diff --git a/src/baby_monitor/repositories/invitation/__init__.py b/src/baby_monitor/repositories/invitation/__init__.py index 2ddabc3..b692596 100644 --- a/src/baby_monitor/repositories/invitation/__init__.py +++ b/src/baby_monitor/repositories/invitation/__init__.py @@ -1,7 +1,10 @@ """Invitation repository implementations.""" from baby_monitor.repositories.invitation.sqlite_invitation import ( - SQLiteInvitationRepository, + DatabaseInvitationRepository, ) -__all__ = ["SQLiteInvitationRepository"] +# Keep old name for backward compatibility +SQLiteInvitationRepository = DatabaseInvitationRepository + +__all__ = ["DatabaseInvitationRepository", "SQLiteInvitationRepository"] diff --git a/src/baby_monitor/repositories/invitation/sqlite_invitation.py b/src/baby_monitor/repositories/invitation/sqlite_invitation.py index e51882c..c5d0e51 100644 --- a/src/baby_monitor/repositories/invitation/sqlite_invitation.py +++ b/src/baby_monitor/repositories/invitation/sqlite_invitation.py @@ -1,25 +1,49 @@ -"""SQLite implementation of invitation repository. +"""Database-agnostic implementation of invitation repository. -Note: SQLite does not support timezone-aware datetimes natively. -All datetimes are stored as naive UTC and converted at the application layer. +Supports both SQLite and PostgreSQL: +- SQLite: stores datetimes as naive UTC +- PostgreSQL: stores datetimes as timezone-aware + +The repository automatically adapts by checking the timezone awareness +of datetimes returned from the database. """ from datetime import datetime, UTC from sqlalchemy.orm import Session from baby_monitor.models.invitation import Invitation -from baby_monitor.repositories.interfaces.invitation_repository_interface import ( +from baby_monitor.repositories.interfaces import ( InvitationRepositoryInterface, ) -class SQLiteInvitationRepository(InvitationRepositoryInterface): - """SQLite implementation for managing invitation tokens.""" +class DatabaseInvitationRepository(InvitationRepositoryInterface): + """Database implementation for managing invitation tokens. + + Works with both SQLite and PostgreSQL databases. + """ def __init__(self, db: Session) -> None: """Initialize the repository with a database session.""" self.db = db + def _get_now_utc(self, reference_dt: datetime) -> datetime: + """ + Get current UTC time matching the timezone awareness of reference. + + Args: + reference_dt: A datetime from DB to match timezone format + + Returns: + Current UTC time (naive for SQLite, aware for PostgreSQL) + """ + now = datetime.now(UTC) + if reference_dt.tzinfo is None: + # SQLite: return naive UTC + return now.replace(tzinfo=None) + # PostgreSQL: return timezone-aware + return now + def create_invitation( self, token: str, created_by_user_id: int, expires_at: datetime ) -> None: @@ -58,13 +82,15 @@ class SQLiteInvitationRepository(InvitationRepositoryInterface): Returns: True if valid and not consumed, False otherwise """ - invitation = self.db.query(Invitation).filter(Invitation.token == token).first() + invitation = self.db.query(Invitation).filter( + Invitation.token == token + ).first() if not invitation: return False - # Compare as naive UTC datetimes (SQLite stores without timezone) - now_utc = datetime.now(UTC).replace(tzinfo=None) + # Get current time matching database timezone format + now_utc = self._get_now_utc(invitation.expires_at) if invitation.expires_at < now_utc: return False @@ -84,13 +110,15 @@ class SQLiteInvitationRepository(InvitationRepositoryInterface): Returns: True if successfully consumed, False if invalid or already used """ - invitation = self.db.query(Invitation).filter(Invitation.token == token).first() + invitation = self.db.query(Invitation).filter( + Invitation.token == token + ).first() if not invitation: return False - # Compare as naive UTC datetimes (SQLite stores without timezone) - now_utc = datetime.now(UTC).replace(tzinfo=None) + # Get current time matching database timezone format + now_utc = self._get_now_utc(invitation.expires_at) if invitation.expires_at < now_utc: return False @@ -107,6 +135,16 @@ class SQLiteInvitationRepository(InvitationRepositoryInterface): def cleanup_expired(self) -> None: """Remove expired invitation tokens from storage.""" - now_utc = datetime.now(UTC).replace(tzinfo=None) - self.db.query(Invitation).filter(Invitation.expires_at < now_utc).delete() + # For cleanup, we need to get a sample to determine format + # Get any invitation to check timezone format + sample = self.db.query(Invitation).first() + if sample: + now_utc = self._get_now_utc(sample.expires_at) + else: + # No invitations, use naive UTC (safe default) + now_utc = datetime.now(UTC).replace(tzinfo=None) + + self.db.query(Invitation).filter( + Invitation.expires_at < now_utc + ).delete() self.db.commit() diff --git a/src/baby_monitor/repositories/sleep/sqlite_sleep.py b/src/baby_monitor/repositories/sleep/sqlite_sleep.py index 0e641d0..da1024f 100644 --- a/src/baby_monitor/repositories/sleep/sqlite_sleep.py +++ b/src/baby_monitor/repositories/sleep/sqlite_sleep.py @@ -1,9 +1,12 @@ -"""SQLite implementation of sleep repository.""" +"""Database-agnostic implementation of sleep repository. + +Works with both SQLite and PostgreSQL. +""" from datetime import datetime from sqlalchemy.orm import Session -from baby_monitor.repositories.interfaces.sleep_repository_interface import ( +from baby_monitor.repositories.interfaces import ( SleepRepositoryInterface, ) from baby_monitor.models.db.sleep import Sleep @@ -11,8 +14,11 @@ from baby_monitor.models.db.child import Child from baby_monitor.models.db.child_parent import ChildParent -class SQLiteSleepRepository(SleepRepositoryInterface): - """SQLite implementation for sleep log data access.""" +class DatabaseSleepRepository(SleepRepositoryInterface): + """Database implementation for sleep log data access. + + Works with both SQLite and PostgreSQL databases. + """ def __init__(self, db: Session): self.db = db diff --git a/src/baby_monitor/repositories/user/postgresql_user.py b/src/baby_monitor/repositories/user/postgresql_user.py deleted file mode 100644 index 46491ff..0000000 --- a/src/baby_monitor/repositories/user/postgresql_user.py +++ /dev/null @@ -1,64 +0,0 @@ -"""PostgreSQL user repository (future implementation).""" - -from sqlalchemy.orm import Session - -from baby_monitor.repositories.interfaces import UserRepositoryInterface -from baby_monitor.models.db.user import User - - -class PostgreSQLUserRepository(UserRepositoryInterface): - """ - PostgreSQL-based user repository. - - This implementation uses the same SQLAlchemy models as SQLite, - just with a different database engine. - - To use this implementation: - 1. Add psycopg2-binary dependency - 2. Update DATABASE_URL in database.py: - DATABASE_URL = os.getenv( - "DATABASE_URL", - "postgresql://user:password@localhost:5432/baby_monitor" - ) - 3. Replace SQLiteUserRepository with this in dependencies - """ - - def __init__(self, db: Session): - self.db = db - - def get_by_username(self, username: str) -> dict | None: - """Get user by username.""" - user = self.db.query(User).filter(User.username == username).first() - if user: - return { - "id": user.id, - "username": user.username, - "hashed_password": user.hashed_password, - "created_at": user.created_at, - } - return None - - def create(self, username: str, hashed_password: str) -> dict: - """Create a new user.""" - user = User(username=username, hashed_password=hashed_password) - self.db.add(user) - self.db.commit() - self.db.refresh(user) - return { - "id": user.id, - "username": user.username, - "hashed_password": user.hashed_password, - "created_at": user.created_at, - } - - def get_by_id(self, user_id: int) -> dict | None: - """Get user by ID.""" - user = self.db.query(User).filter(User.id == user_id).first() - if user: - return { - "id": user.id, - "username": user.username, - "hashed_password": user.hashed_password, - "created_at": user.created_at, - } - return None diff --git a/src/baby_monitor/repositories/user/sqlite_user.py b/src/baby_monitor/repositories/user/sqlite_user.py index 2cf6ac4..58fbf3b 100644 --- a/src/baby_monitor/repositories/user/sqlite_user.py +++ b/src/baby_monitor/repositories/user/sqlite_user.py @@ -1,4 +1,7 @@ -"""SQLite implementation of user repository.""" +"""Database-agnostic implementation of user repository. + +Works with both SQLite and PostgreSQL. +""" from sqlalchemy.orm import Session @@ -6,8 +9,11 @@ from baby_monitor.repositories.interfaces import UserRepositoryInterface from baby_monitor.models.db.user import User -class SQLiteUserRepository(UserRepositoryInterface): - """SQLite-based user repository.""" +class DatabaseUserRepository(UserRepositoryInterface): + """Database-based user repository. + + Works with both SQLite and PostgreSQL databases. + """ def __init__(self, db: Session): self.db = db diff --git a/src/baby_monitor/routers/admin.py b/src/baby_monitor/routers/admin.py index 49d697b..31847c9 100644 --- a/src/baby_monitor/routers/admin.py +++ b/src/baby_monitor/routers/admin.py @@ -7,22 +7,12 @@ from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from baby_monitor.routers.auth import verify_admin -from baby_monitor.repositories.dependencies.get_invitation_repository import ( +from baby_monitor.repositories.dependencies import ( get_invitation_repository, -) -from baby_monitor.repositories.dependencies.get_user_repository import ( get_user_repository, -) -from baby_monitor.repositories.dependencies.get_child_repository import ( get_child_repository, -) -from baby_monitor.repositories.dependencies.get_feeding_repository import ( get_feeding_repository, -) -from baby_monitor.repositories.dependencies.get_diaper_change_repository import ( get_diaper_change_repository, -) -from baby_monitor.repositories.dependencies.get_sleep_repository import ( get_sleep_repository, ) from baby_monitor.repositories.interfaces import ( diff --git a/src/baby_monitor/routers/auth.py b/src/baby_monitor/routers/auth.py index 294d0c5..33da6ac 100644 --- a/src/baby_monitor/routers/auth.py +++ b/src/baby_monitor/routers/auth.py @@ -1,8 +1,8 @@ """Authentication router for login and user management.""" import secrets -from fastapi import APIRouter, HTTPException, Depends, Header from typing import Annotated +from fastapi import APIRouter, HTTPException, Depends, Header from pydantic import BaseModel from baby_monitor.models.auth import ( @@ -29,7 +29,7 @@ from baby_monitor.repositories.interfaces import ( DiaperChangeRepositoryInterface, SleepRepositoryInterface, ) -from baby_monitor.utils.password import hash_password, verify_password +from baby_monitor.utils import hash_password, verify_password router = APIRouter(prefix="/api", tags=["authentication"]) diff --git a/src/baby_monitor/routers/child.py b/src/baby_monitor/routers/child.py index ead85c6..13309a4 100644 --- a/src/baby_monitor/routers/child.py +++ b/src/baby_monitor/routers/child.py @@ -8,14 +8,14 @@ from pydantic import BaseModel from baby_monitor.models.child import CreateChildRequest, ChildResponse from baby_monitor.routers.auth import verify_token -from baby_monitor.repositories.child.sqlite_child import SQLiteChildRepository -from baby_monitor.repositories.dependencies.get_child_repository import ( - get_child_repository, +from baby_monitor.repositories.interfaces import ( + ChildRepositoryInterface, + ChildInvitationRepositoryInterface ) -from baby_monitor.repositories.dependencies.get_child_invitation_repository import ( # noqa: E501 +from baby_monitor.repositories.dependencies import ( + get_child_repository, get_child_invitation_repository, ) -from baby_monitor.repositories.interfaces import ChildInvitationRepositoryInterface # noqa: E501 router = APIRouter(prefix="/api/children", tags=["children"]) @@ -45,7 +45,9 @@ class RedeemChildInvitationRequest(BaseModel): def create_child( request: CreateChildRequest, user_id: Annotated[int, Depends(verify_token)], - child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], + child_repo: Annotated[ + ChildRepositoryInterface, Depends(get_child_repository) + ], ) -> ChildResponse: """Create a new child for the authenticated user.""" child = child_repo.create( @@ -61,7 +63,9 @@ def create_child( @router.get("", response_model=list[ChildResponse]) def get_user_children( user_id: Annotated[int, Depends(verify_token)], - child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], + child_repo: Annotated[ + ChildRepositoryInterface, Depends(get_child_repository) + ], ) -> list[ChildResponse]: """Get all children for the authenticated user.""" children = child_repo.get_by_user_id(user_id) @@ -72,7 +76,9 @@ def get_user_children( def get_child( child_id: int, user_id: Annotated[int, Depends(verify_token)], - child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], + child_repo: Annotated[ + ChildRepositoryInterface, Depends(get_child_repository) + ], ) -> ChildResponse: """Get a specific child by ID.""" child = child_repo.get_by_id(child_id) @@ -93,7 +99,9 @@ def update_child( child_id: int, request: CreateChildRequest, user_id: Annotated[int, Depends(verify_token)], - child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], + child_repo: Annotated[ + ChildRepositoryInterface, Depends(get_child_repository) + ], ) -> ChildResponse: """Update a child's information.""" # First check if child exists and belongs to user @@ -124,10 +132,13 @@ def update_child( def create_child_invitation( request: CreateChildInvitationRequest, user_id: Annotated[int, Depends(verify_token)], - child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], # noqa: E501 + child_repo: Annotated[ + ChildRepositoryInterface, Depends(get_child_repository) + ], invitation_repo: Annotated[ - ChildInvitationRepositoryInterface, Depends(get_child_invitation_repository) - ], # noqa: E501 + ChildInvitationRepositoryInterface, + Depends(get_child_invitation_repository), + ], ) -> ChildInvitationResponse: """Create an invitation code to share child access with another user.""" # Verify the child exists and user has access @@ -165,10 +176,13 @@ def create_child_invitation( def redeem_child_invitation( request: RedeemChildInvitationRequest, user_id: Annotated[int, Depends(verify_token)], - child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], # noqa: E501 + child_repo: Annotated[ + ChildRepositoryInterface, Depends(get_child_repository) + ], invitation_repo: Annotated[ - ChildInvitationRepositoryInterface, Depends(get_child_invitation_repository) - ], # noqa: E501 + ChildInvitationRepositoryInterface, + Depends(get_child_invitation_repository), + ], ) -> dict: """Redeem an invitation code to gain access to a child.""" # Verify the invitation code diff --git a/src/baby_monitor/routers/diaper_change.py b/src/baby_monitor/routers/diaper_change.py index a62f063..876de8a 100644 --- a/src/baby_monitor/routers/diaper_change.py +++ b/src/baby_monitor/routers/diaper_change.py @@ -9,17 +9,15 @@ from baby_monitor.models.diaper_change import ( DiaperChangeResponse, ) from baby_monitor.routers.auth import verify_token -from baby_monitor.repositories.diaper_change.sqlite_diaper_change import ( - SQLiteDiaperChangeRepository, +from baby_monitor.repositories.interfaces import ( + DiaperChangeRepositoryInterface, + ChildRepositoryInterface, ) -from baby_monitor.repositories.dependencies.get_diaper_change_repository import ( # noqa: E501 +from baby_monitor.repositories.dependencies import ( get_diaper_change_repository, -) -from baby_monitor.repositories.child.sqlite_child import SQLiteChildRepository -from baby_monitor.repositories.dependencies.get_child_repository import ( get_child_repository, ) -from baby_monitor.utils.access_control import verify_child_access +from baby_monitor.utils.verify_child_access import verify_child_access router = APIRouter(prefix="/api/diaper-changes", tags=["diaper-changes"]) @@ -29,9 +27,11 @@ def create_diaper_change( request: CreateDiaperChangeRequest, user_id: Annotated[int, Depends(verify_token)], diaper_repo: Annotated[ - SQLiteDiaperChangeRepository, Depends(get_diaper_change_repository) + DiaperChangeRepositoryInterface, Depends(get_diaper_change_repository) + ], + child_repo: Annotated[ + ChildRepositoryInterface, Depends(get_child_repository) ], - child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], ) -> DiaperChangeResponse: """Create a new diaper change log entry.""" # Verify the child belongs to the authenticated user @@ -53,7 +53,7 @@ def create_diaper_change( def get_user_diaper_changes( user_id: Annotated[int, Depends(verify_token)], diaper_repo: Annotated[ - SQLiteDiaperChangeRepository, Depends(get_diaper_change_repository) + DiaperChangeRepositoryInterface, Depends(get_diaper_change_repository) ], ) -> list[DiaperChangeResponse]: """Get all diaper change logs for the authenticated user's children.""" @@ -66,9 +66,11 @@ def get_diaper_change( diaper_change_id: int, user_id: Annotated[int, Depends(verify_token)], diaper_repo: Annotated[ - SQLiteDiaperChangeRepository, Depends(get_diaper_change_repository) + DiaperChangeRepositoryInterface, Depends(get_diaper_change_repository) + ], + child_repo: Annotated[ + ChildRepositoryInterface, Depends(get_child_repository) ], - child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], ) -> DiaperChangeResponse: """Get a specific diaper change log by ID.""" diaper_change = diaper_repo.get_by_id(diaper_change_id) @@ -88,9 +90,11 @@ def update_diaper_change( request: UpdateDiaperChangeRequest, user_id: Annotated[int, Depends(verify_token)], diaper_repo: Annotated[ - SQLiteDiaperChangeRepository, Depends(get_diaper_change_repository) + DiaperChangeRepositoryInterface, Depends(get_diaper_change_repository) + ], + child_repo: Annotated[ + ChildRepositoryInterface, Depends(get_child_repository) ], - child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], ) -> DiaperChangeResponse: """Update an existing diaper change log.""" diaper_change = diaper_repo.get_by_id(diaper_change_id) @@ -123,9 +127,11 @@ def delete_diaper_change( diaper_change_id: int, user_id: Annotated[int, Depends(verify_token)], diaper_repo: Annotated[ - SQLiteDiaperChangeRepository, Depends(get_diaper_change_repository) + DiaperChangeRepositoryInterface, Depends(get_diaper_change_repository) + ], + child_repo: Annotated[ + ChildRepositoryInterface, Depends(get_child_repository) ], - child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], ) -> None: """Delete a diaper change log.""" diaper_change = diaper_repo.get_by_id(diaper_change_id) diff --git a/src/baby_monitor/routers/feeding.py b/src/baby_monitor/routers/feeding.py index c205738..f2d8313 100644 --- a/src/baby_monitor/routers/feeding.py +++ b/src/baby_monitor/routers/feeding.py @@ -9,17 +9,15 @@ from baby_monitor.models.feeding import ( FeedingResponse, ) from baby_monitor.routers.auth import verify_token -from baby_monitor.repositories.feeding.sqlite_feeding import ( - SQLiteFeedingRepository, +from baby_monitor.repositories.interfaces import ( + ChildRepositoryInterface, + FeedingRepositoryInterface, ) -from baby_monitor.repositories.dependencies.get_feeding_repository import ( +from baby_monitor.repositories.dependencies import ( get_feeding_repository, -) -from baby_monitor.repositories.child.sqlite_child import SQLiteChildRepository -from baby_monitor.repositories.dependencies.get_child_repository import ( get_child_repository, ) -from baby_monitor.utils.access_control import verify_child_access +from baby_monitor.utils import verify_child_access router = APIRouter(prefix="/api/feedings", tags=["feedings"]) @@ -28,8 +26,12 @@ router = APIRouter(prefix="/api/feedings", tags=["feedings"]) def create_feeding( request: CreateFeedingRequest, user_id: Annotated[int, Depends(verify_token)], - feeding_repo: Annotated[SQLiteFeedingRepository, Depends(get_feeding_repository)], - child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], + feeding_repo: Annotated[ + FeedingRepositoryInterface, Depends(get_feeding_repository) + ], + child_repo: Annotated[ + ChildRepositoryInterface, Depends(get_child_repository) + ], ) -> FeedingResponse: """Create a new feeding log entry.""" # Verify the child belongs to the authenticated user @@ -48,8 +50,12 @@ def create_feeding( @router.get("/active", response_model=FeedingResponse | None) def get_active_feeding( user_id: Annotated[int, Depends(verify_token)], - feeding_repo: Annotated[SQLiteFeedingRepository, Depends(get_feeding_repository)], - child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], + feeding_repo: Annotated[ + FeedingRepositoryInterface, Depends(get_feeding_repository) + ], + child_repo: Annotated[ + ChildRepositoryInterface, Depends(get_child_repository) + ], ) -> FeedingResponse | None: """Get the current active feeding (where end_time is null) for the user.""" feedings = feeding_repo.get_by_user_id(user_id) @@ -70,7 +76,9 @@ def get_active_feeding( @router.get("", response_model=list[FeedingResponse]) def get_user_feedings( user_id: Annotated[int, Depends(verify_token)], - feeding_repo: Annotated[SQLiteFeedingRepository, Depends(get_feeding_repository)], + feeding_repo: Annotated[ + FeedingRepositoryInterface, Depends(get_feeding_repository) + ], ) -> list[FeedingResponse]: """Get all feeding logs for the authenticated user's children.""" feedings = feeding_repo.get_by_user_id(user_id) @@ -81,8 +89,12 @@ def get_user_feedings( def get_feeding( feeding_id: int, user_id: Annotated[int, Depends(verify_token)], - feeding_repo: Annotated[SQLiteFeedingRepository, Depends(get_feeding_repository)], - child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], + feeding_repo: Annotated[ + FeedingRepositoryInterface, Depends(get_feeding_repository) + ], + child_repo: Annotated[ + ChildRepositoryInterface, Depends(get_child_repository) + ], ) -> FeedingResponse: """Get a specific feeding log by ID.""" feeding = feeding_repo.get_by_id(feeding_id) @@ -101,8 +113,12 @@ def update_feeding( feeding_id: int, request: UpdateFeedingRequest, user_id: Annotated[int, Depends(verify_token)], - feeding_repo: Annotated[SQLiteFeedingRepository, Depends(get_feeding_repository)], - child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], + feeding_repo: Annotated[ + FeedingRepositoryInterface, Depends(get_feeding_repository) + ], + child_repo: Annotated[ + ChildRepositoryInterface, Depends(get_child_repository) + ], ) -> FeedingResponse: """Update a feeding log entry.""" feeding = feeding_repo.get_by_id(feeding_id) @@ -114,11 +130,14 @@ def update_feeding( verify_child_access(child_repo, feeding["child_id"], user_id) # Update the feeding + feeding_type_value = ( + request.feeding_type.value if request.feeding_type else None + ) updated_feeding = feeding_repo.update( feeding_id=feeding_id, start_time=request.start_time, end_time=request.end_time, - feeding_type=request.feeding_type.value if request.feeding_type else None, + feeding_type=feeding_type_value, ) if not updated_feeding: @@ -131,8 +150,12 @@ def update_feeding( def delete_feeding( feeding_id: int, user_id: Annotated[int, Depends(verify_token)], - feeding_repo: Annotated[SQLiteFeedingRepository, Depends(get_feeding_repository)], - child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], + feeding_repo: Annotated[ + FeedingRepositoryInterface, Depends(get_feeding_repository) + ], + child_repo: Annotated[ + ChildRepositoryInterface, Depends(get_child_repository) + ], ) -> None: """Delete a feeding log entry.""" feeding = feeding_repo.get_by_id(feeding_id) diff --git a/src/baby_monitor/routers/sleep.py b/src/baby_monitor/routers/sleep.py index a2308b2..9ca901f 100644 --- a/src/baby_monitor/routers/sleep.py +++ b/src/baby_monitor/routers/sleep.py @@ -9,15 +9,15 @@ from baby_monitor.models.sleep import ( SleepResponse, ) from baby_monitor.routers.auth import verify_token -from baby_monitor.repositories.sleep.sqlite_sleep import SQLiteSleepRepository -from baby_monitor.repositories.dependencies.get_sleep_repository import ( - get_sleep_repository, +from baby_monitor.repositories.interfaces import ( + SleepRepositoryInterface, + ChildRepositoryInterface, ) -from baby_monitor.repositories.child.sqlite_child import SQLiteChildRepository -from baby_monitor.repositories.dependencies.get_child_repository import ( +from baby_monitor.repositories.dependencies import ( + get_sleep_repository, get_child_repository, ) -from baby_monitor.utils.access_control import verify_child_access +from baby_monitor.utils import verify_child_access router = APIRouter(prefix="/api/sleep", tags=["sleep"]) @@ -26,8 +26,12 @@ router = APIRouter(prefix="/api/sleep", tags=["sleep"]) def create_sleep( request: CreateSleepRequest, user_id: Annotated[int, Depends(verify_token)], - sleep_repo: Annotated[SQLiteSleepRepository, Depends(get_sleep_repository)], - child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], + sleep_repo: Annotated[ + SleepRepositoryInterface, Depends(get_sleep_repository) + ], + child_repo: Annotated[ + ChildRepositoryInterface, Depends(get_child_repository) + ], ) -> SleepResponse: """Create a new sleep log entry.""" # Verify the child belongs to the authenticated user @@ -44,8 +48,12 @@ def create_sleep( @router.get("/active", response_model=SleepResponse | None) def get_active_sleep( user_id: Annotated[int, Depends(verify_token)], - sleep_repo: Annotated[SQLiteSleepRepository, Depends(get_sleep_repository)], - child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], + sleep_repo: Annotated[ + SleepRepositoryInterface, Depends(get_sleep_repository) + ], + child_repo: Annotated[ + ChildRepositoryInterface, Depends(get_child_repository) + ], ) -> SleepResponse | None: """Get the current active sleep (where end_time is null) for the user.""" sleeps = sleep_repo.get_by_user_id(user_id) @@ -66,7 +74,9 @@ def get_active_sleep( @router.get("", response_model=list[SleepResponse]) def get_user_sleeps( user_id: Annotated[int, Depends(verify_token)], - sleep_repo: Annotated[SQLiteSleepRepository, Depends(get_sleep_repository)], + sleep_repo: Annotated[ + SleepRepositoryInterface, Depends(get_sleep_repository) + ], ) -> list[SleepResponse]: """Get all sleep logs for the authenticated user's children.""" sleeps = sleep_repo.get_by_user_id(user_id) @@ -77,8 +87,12 @@ def get_user_sleeps( def get_sleep( sleep_id: int, user_id: Annotated[int, Depends(verify_token)], - sleep_repo: Annotated[SQLiteSleepRepository, Depends(get_sleep_repository)], - child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], + sleep_repo: Annotated[ + SleepRepositoryInterface, Depends(get_sleep_repository) + ], + child_repo: Annotated[ + ChildRepositoryInterface, Depends(get_child_repository) + ], ) -> SleepResponse: """Get a specific sleep log by ID.""" sleep = sleep_repo.get_by_id(sleep_id) @@ -97,8 +111,12 @@ def update_sleep( sleep_id: int, request: UpdateSleepRequest, user_id: Annotated[int, Depends(verify_token)], - sleep_repo: Annotated[SQLiteSleepRepository, Depends(get_sleep_repository)], - child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], + sleep_repo: Annotated[ + SleepRepositoryInterface, Depends(get_sleep_repository) + ], + child_repo: Annotated[ + ChildRepositoryInterface, Depends(get_child_repository) + ], ) -> SleepResponse: """Update an existing sleep log.""" sleep = sleep_repo.get_by_id(sleep_id) @@ -124,8 +142,12 @@ def update_sleep( def delete_sleep( sleep_id: int, user_id: Annotated[int, Depends(verify_token)], - sleep_repo: Annotated[SQLiteSleepRepository, Depends(get_sleep_repository)], - child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], + sleep_repo: Annotated[ + SleepRepositoryInterface, Depends(get_sleep_repository) + ], + child_repo: Annotated[ + ChildRepositoryInterface, Depends(get_child_repository) + ], ) -> None: """Delete a sleep log.""" sleep = sleep_repo.get_by_id(sleep_id) diff --git a/src/baby_monitor/utils/__init__.py b/src/baby_monitor/utils/__init__.py new file mode 100644 index 0000000..b16d2ad --- /dev/null +++ b/src/baby_monitor/utils/__init__.py @@ -0,0 +1,10 @@ +"""Utility functions for the baby monitor application.""" +from .hash_password import hash_password +from .verify_child_access import verify_child_access +from .verify_password import verify_password + +__all__ = [ + "verify_child_access", + "hash_password", + "verify_password", +] diff --git a/src/baby_monitor/utils/hash_password.py b/src/baby_monitor/utils/hash_password.py new file mode 100644 index 0000000..1f80f7f --- /dev/null +++ b/src/baby_monitor/utils/hash_password.py @@ -0,0 +1,17 @@ +"""Definition of hash_password function.""" + +import bcrypt + + +def hash_password(password: str) -> str: + """Hash a password using bcrypt. + + Args: + password: Plain text password to hash + + Returns: + Hashed password as a string + """ + salt = bcrypt.gensalt() + hashed: bytes = bcrypt.hashpw(password.encode("utf-8"), salt) + return hashed.decode("utf-8") diff --git a/src/baby_monitor/utils/access_control.py b/src/baby_monitor/utils/verify_child_access.py similarity index 75% rename from src/baby_monitor/utils/access_control.py rename to src/baby_monitor/utils/verify_child_access.py index 476023a..4638284 100644 --- a/src/baby_monitor/utils/access_control.py +++ b/src/baby_monitor/utils/verify_child_access.py @@ -1,11 +1,13 @@ -"""Access control utility functions for verifying resource ownership.""" +"""Definition of verify_child_access function.""" from fastapi import HTTPException -from baby_monitor.repositories.child.sqlite_child import SQLiteChildRepository +from baby_monitor.repositories.interfaces import ( + ChildRepositoryInterface, +) def verify_child_access( - child_repo: SQLiteChildRepository, child_id: int, user_id: int + child_repo: ChildRepositoryInterface, child_id: int, user_id: int ) -> None: """Verify that user has access to the child. diff --git a/src/baby_monitor/utils/password.py b/src/baby_monitor/utils/verify_password.py similarity index 54% rename from src/baby_monitor/utils/password.py rename to src/baby_monitor/utils/verify_password.py index 2294c6b..85c5cc0 100644 --- a/src/baby_monitor/utils/password.py +++ b/src/baby_monitor/utils/verify_password.py @@ -1,22 +1,8 @@ -"""Password hashing utilities using bcrypt.""" +"""Definition of verify_password function.""" import bcrypt -def hash_password(password: str) -> str: - """Hash a password using bcrypt. - - Args: - password: Plain text password to hash - - Returns: - Hashed password as a string - """ - salt = bcrypt.gensalt() - hashed: bytes = bcrypt.hashpw(password.encode("utf-8"), salt) - return hashed.decode("utf-8") - - def verify_password(password: str, hashed_password: str) -> bool: """Verify a password against a hashed password.