implemented postgres support in repositories

This commit is contained in:
Brian Bjarke Jensen
2025-11-11 18:52:19 +01:00
parent 65655db034
commit e7ed89e04e
31 changed files with 364 additions and 236 deletions
+1 -1
View File
@@ -1,8 +1,8 @@
"""Sleep database model.""" """Sleep database model."""
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from sqlalchemy import Column, Integer, DateTime, ForeignKey
from datetime import datetime, UTC from datetime import datetime, UTC
from sqlalchemy import Column, Integer, DateTime, ForeignKey
if TYPE_CHECKING: if TYPE_CHECKING:
from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import DeclarativeBase
@@ -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 datetime import datetime, UTC
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from baby_monitor.repositories.interfaces.child_repository_interface import ( from baby_monitor.repositories.interfaces import (
ChildRepositoryInterface, ChildRepositoryInterface,
) )
from baby_monitor.models.db.child import Child from baby_monitor.models.db.child import Child
from baby_monitor.models.db.child_parent import ChildParent from baby_monitor.models.db.child_parent import ChildParent
class SQLiteChildRepository(ChildRepositoryInterface): class DatabaseChildRepository(ChildRepositoryInterface):
"""SQLite implementation for child data access.""" """Database implementation for child data access.
Works with both SQLite and PostgreSQL databases.
"""
def __init__(self, db: Session): def __init__(self, db: Session):
self.db = db self.db = db
@@ -1,7 +1,13 @@
"""Child invitation repository implementations.""" """Child invitation repository implementations."""
from baby_monitor.repositories.child_invitation.sqlite_child_invitation import ( # noqa: E501 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",
]
@@ -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 datetime import datetime, UTC
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from baby_monitor.models.child_invitation import ChildInvitation from baby_monitor.models.child_invitation import ChildInvitation
from baby_monitor.repositories.interfaces.child_invitation import ( from baby_monitor.repositories.interfaces import (
ChildInvitationRepositoryInterface, ChildInvitationRepositoryInterface,
) )
class SQLiteChildInvitationRepository(ChildInvitationRepositoryInterface): class DatabaseChildInvitationRepository(ChildInvitationRepositoryInterface):
"""SQLite implementation of child invitation repository.""" """Database implementation of child invitation repository.
Works with both SQLite and PostgreSQL databases.
"""
def __init__(self, db: Session): def __init__(self, db: Session):
"""Initialize the repository with a database session.""" """Initialize the repository with a database session."""
self.db = db 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( def create_invitation(
self, self,
code: str, code: str,
@@ -59,7 +84,9 @@ class SQLiteChildInvitationRepository(ChildInvitationRepositoryInterface):
def verify_invitation(self, code: str) -> bool: def verify_invitation(self, code: str) -> bool:
"""Verify if an invitation code is valid and not expired.""" """Verify if an invitation code is valid and not expired."""
invitation = ( invitation = (
self.db.query(ChildInvitation).filter(ChildInvitation.code == code).first() self.db.query(ChildInvitation)
.filter(ChildInvitation.code == code)
.first()
) )
if not invitation: if not invitation:
@@ -68,8 +95,8 @@ class SQLiteChildInvitationRepository(ChildInvitationRepositoryInterface):
if invitation.is_consumed: if invitation.is_consumed:
return False return False
# Compare as naive UTC datetimes (SQLite stores without timezone) # Get current time matching database timezone format
now_utc = datetime.now(UTC).replace(tzinfo=None) now_utc = self._get_now_utc(invitation.expires_at)
if invitation.expires_at < now_utc: if invitation.expires_at < now_utc:
return False return False
@@ -4,13 +4,16 @@ from typing import Annotated
from fastapi import Depends from fastapi import Depends
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from baby_monitor.repositories.child_invitation.sqlite_child_invitation import ( # noqa: E501 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 from baby_monitor.repositories.dependencies.get_database import get_database
def get_child_invitation_repository( def get_child_invitation_repository(
db: Annotated[Session, Depends(get_database)], db: Annotated[Session, Depends(get_database)],
) -> SQLiteChildInvitationRepository: ) -> ChildInvitationRepositoryInterface:
"""Get child invitation repository instance.""" """Get child invitation repository instance (works with any database)."""
return SQLiteChildInvitationRepository(db) return DatabaseChildInvitationRepository(db)
@@ -5,11 +5,14 @@ from fastapi import Depends
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from baby_monitor.repositories.dependencies.get_database import get_database 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( def get_child_repository(
db: Annotated[Session, Depends(get_database)], db: Annotated[Session, Depends(get_database)],
) -> SQLiteChildRepository: ) -> ChildRepositoryInterface:
"""Get child repository instance.""" """Get child repository instance (works with any database)."""
return SQLiteChildRepository(db) return DatabaseChildRepository(db)
@@ -45,7 +45,7 @@ Base: "type[DeclarativeBase]" = declarative_base()
def _ensure_admin_user() -> None: def _ensure_admin_user() -> None:
"""Create or update admin user from environment variables.""" """Create or update admin user from environment variables."""
from baby_monitor.models.db.user import User 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_username = os.getenv("ADMIN_USERNAME")
admin_password = os.getenv("ADMIN_PASSWORD") admin_password = os.getenv("ADMIN_PASSWORD")
@@ -6,12 +6,15 @@ from sqlalchemy.orm import Session
from baby_monitor.repositories.dependencies.get_database import get_database from baby_monitor.repositories.dependencies.get_database import get_database
from baby_monitor.repositories.diaper_change.sqlite_diaper_change import ( 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( def get_diaper_change_repository(
db: Annotated[Session, Depends(get_database)], db: Annotated[Session, Depends(get_database)],
) -> SQLiteDiaperChangeRepository: ) -> DiaperChangeRepositoryInterface:
"""Get diaper change repository instance.""" """Get diaper change repository instance (works with any database)."""
return SQLiteDiaperChangeRepository(db) return DatabaseDiaperChangeRepository(db)
@@ -6,12 +6,15 @@ from sqlalchemy.orm import Session
from baby_monitor.repositories.dependencies.get_database import get_database from baby_monitor.repositories.dependencies.get_database import get_database
from baby_monitor.repositories.feeding.sqlite_feeding import ( from baby_monitor.repositories.feeding.sqlite_feeding import (
SQLiteFeedingRepository, DatabaseFeedingRepository,
)
from baby_monitor.repositories.interfaces.feeding_repository_interface import (
FeedingRepositoryInterface,
) )
def get_feeding_repository( def get_feeding_repository(
db: Annotated[Session, Depends(get_database)], db: Annotated[Session, Depends(get_database)],
) -> SQLiteFeedingRepository: ) -> FeedingRepositoryInterface:
"""Get feeding repository instance.""" """Get feeding repository instance (works with any database)."""
return SQLiteFeedingRepository(db) return DatabaseFeedingRepository(db)
@@ -6,13 +6,16 @@ from sqlalchemy.orm import Session
from baby_monitor.repositories.dependencies.get_database import get_database from baby_monitor.repositories.dependencies.get_database import get_database
from baby_monitor.repositories.invitation.sqlite_invitation import ( from baby_monitor.repositories.invitation.sqlite_invitation import (
SQLiteInvitationRepository, DatabaseInvitationRepository,
)
from baby_monitor.repositories.interfaces.invitation_repository_interface import (
InvitationRepositoryInterface,
) )
def get_invitation_repository( def get_invitation_repository(
db: Annotated[Session, Depends(get_database)], db: Annotated[Session, Depends(get_database)],
) -> SQLiteInvitationRepository: ) -> InvitationRepositoryInterface:
""" """
Provide an invitation repository instance. Provide an invitation repository instance.
@@ -20,6 +23,6 @@ def get_invitation_repository(
db: Database session from dependency injection db: Database session from dependency injection
Returns: Returns:
SQLiteInvitationRepository instance InvitationRepositoryInterface instance (works with any database)
""" """
return SQLiteInvitationRepository(db) return DatabaseInvitationRepository(db)
@@ -5,11 +5,14 @@ from fastapi import Depends
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from baby_monitor.repositories.dependencies.get_database import get_database 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( def get_sleep_repository(
db: Annotated[Session, Depends(get_database)], db: Annotated[Session, Depends(get_database)],
) -> SQLiteSleepRepository: ) -> SleepRepositoryInterface:
"""Get sleep repository instance.""" """Get sleep repository instance (works with any database)."""
return SQLiteSleepRepository(db) return DatabaseSleepRepository(db)
@@ -11,7 +11,7 @@ from baby_monitor.repositories.interfaces import (
UserRepositoryInterface, UserRepositoryInterface,
) )
from baby_monitor.repositories.user.sqlite_user import ( from baby_monitor.repositories.user.sqlite_user import (
SQLiteUserRepository, DatabaseUserRepository,
) )
# Singleton instance # Singleton instance
@@ -22,12 +22,12 @@ def get_user_repository(
db: Annotated[Session, Depends(get_database)], db: Annotated[Session, Depends(get_database)],
) -> UserRepositoryInterface: ) -> UserRepositoryInterface:
""" """
Get user repository instance. Get user repository instance (works with any database).
""" """
global _user_repository global _user_repository
if _user_repository is None: if _user_repository is None:
_user_repository = SQLiteUserRepository(db) _user_repository = DatabaseUserRepository(db)
return _user_repository return _user_repository
@@ -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 datetime import datetime
from sqlalchemy.orm import Session 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, DiaperChangeRepositoryInterface,
) )
from baby_monitor.models.db.diaper_change import DiaperChange 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 from baby_monitor.models.db.child_parent import ChildParent
class SQLiteDiaperChangeRepository(DiaperChangeRepositoryInterface): class DatabaseDiaperChangeRepository(DiaperChangeRepositoryInterface):
"""SQLite implementation for diaper change log data access.""" """Database implementation for diaper change log data access.
Works with both SQLite and PostgreSQL databases.
"""
def __init__(self, db: Session): def __init__(self, db: Session):
self.db = db self.db = db
@@ -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 datetime import datetime
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from baby_monitor.repositories.interfaces.feeding_repository_interface import ( from baby_monitor.repositories.interfaces import (
FeedingRepositoryInterface, FeedingRepositoryInterface,
) )
from baby_monitor.models.db.feeding import Feeding 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 from baby_monitor.models.db.child_parent import ChildParent
class SQLiteFeedingRepository(FeedingRepositoryInterface): class DatabaseFeedingRepository(FeedingRepositoryInterface):
"""SQLite implementation for feeding log data access.""" """Database implementation for feeding log data access.
Works with both SQLite and PostgreSQL databases.
"""
def __init__(self, db: Session): def __init__(self, db: Session):
self.db = db self.db = db
@@ -1,6 +1,6 @@
"""Repository interfaces package.""" """Repository interfaces package."""
from .child_invitation import ( from .child_invitation_interface import (
ChildInvitationRepositoryInterface, ChildInvitationRepositoryInterface,
) )
from .credentials_repository_interface import ( from .credentials_repository_interface import (
@@ -1,7 +1,10 @@
"""Invitation repository implementations.""" """Invitation repository implementations."""
from baby_monitor.repositories.invitation.sqlite_invitation import ( from baby_monitor.repositories.invitation.sqlite_invitation import (
SQLiteInvitationRepository, DatabaseInvitationRepository,
) )
__all__ = ["SQLiteInvitationRepository"] # Keep old name for backward compatibility
SQLiteInvitationRepository = DatabaseInvitationRepository
__all__ = ["DatabaseInvitationRepository", "SQLiteInvitationRepository"]
@@ -1,25 +1,49 @@
"""SQLite implementation of invitation repository. """Database-agnostic implementation of invitation repository.
Note: SQLite does not support timezone-aware datetimes natively. Supports both SQLite and PostgreSQL:
All datetimes are stored as naive UTC and converted at the application layer. - 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 datetime import datetime, UTC
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from baby_monitor.models.invitation import Invitation from baby_monitor.models.invitation import Invitation
from baby_monitor.repositories.interfaces.invitation_repository_interface import ( from baby_monitor.repositories.interfaces import (
InvitationRepositoryInterface, InvitationRepositoryInterface,
) )
class SQLiteInvitationRepository(InvitationRepositoryInterface): class DatabaseInvitationRepository(InvitationRepositoryInterface):
"""SQLite implementation for managing invitation tokens.""" """Database implementation for managing invitation tokens.
Works with both SQLite and PostgreSQL databases.
"""
def __init__(self, db: Session) -> None: def __init__(self, db: Session) -> None:
"""Initialize the repository with a database session.""" """Initialize the repository with a database session."""
self.db = db 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( def create_invitation(
self, token: str, created_by_user_id: int, expires_at: datetime self, token: str, created_by_user_id: int, expires_at: datetime
) -> None: ) -> None:
@@ -58,13 +82,15 @@ class SQLiteInvitationRepository(InvitationRepositoryInterface):
Returns: Returns:
True if valid and not consumed, False otherwise 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: if not invitation:
return False return False
# Compare as naive UTC datetimes (SQLite stores without timezone) # Get current time matching database timezone format
now_utc = datetime.now(UTC).replace(tzinfo=None) now_utc = self._get_now_utc(invitation.expires_at)
if invitation.expires_at < now_utc: if invitation.expires_at < now_utc:
return False return False
@@ -84,13 +110,15 @@ class SQLiteInvitationRepository(InvitationRepositoryInterface):
Returns: Returns:
True if successfully consumed, False if invalid or already used 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: if not invitation:
return False return False
# Compare as naive UTC datetimes (SQLite stores without timezone) # Get current time matching database timezone format
now_utc = datetime.now(UTC).replace(tzinfo=None) now_utc = self._get_now_utc(invitation.expires_at)
if invitation.expires_at < now_utc: if invitation.expires_at < now_utc:
return False return False
@@ -107,6 +135,16 @@ class SQLiteInvitationRepository(InvitationRepositoryInterface):
def cleanup_expired(self) -> None: def cleanup_expired(self) -> None:
"""Remove expired invitation tokens from storage.""" """Remove expired invitation tokens from storage."""
now_utc = datetime.now(UTC).replace(tzinfo=None) # For cleanup, we need to get a sample to determine format
self.db.query(Invitation).filter(Invitation.expires_at < now_utc).delete() # 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() self.db.commit()
@@ -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 datetime import datetime
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from baby_monitor.repositories.interfaces.sleep_repository_interface import ( from baby_monitor.repositories.interfaces import (
SleepRepositoryInterface, SleepRepositoryInterface,
) )
from baby_monitor.models.db.sleep import Sleep 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 from baby_monitor.models.db.child_parent import ChildParent
class SQLiteSleepRepository(SleepRepositoryInterface): class DatabaseSleepRepository(SleepRepositoryInterface):
"""SQLite implementation for sleep log data access.""" """Database implementation for sleep log data access.
Works with both SQLite and PostgreSQL databases.
"""
def __init__(self, db: Session): def __init__(self, db: Session):
self.db = db self.db = db
@@ -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
@@ -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 from sqlalchemy.orm import Session
@@ -6,8 +9,11 @@ from baby_monitor.repositories.interfaces import UserRepositoryInterface
from baby_monitor.models.db.user import User from baby_monitor.models.db.user import User
class SQLiteUserRepository(UserRepositoryInterface): class DatabaseUserRepository(UserRepositoryInterface):
"""SQLite-based user repository.""" """Database-based user repository.
Works with both SQLite and PostgreSQL databases.
"""
def __init__(self, db: Session): def __init__(self, db: Session):
self.db = db self.db = db
+1 -11
View File
@@ -7,22 +7,12 @@ from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel from pydantic import BaseModel
from baby_monitor.routers.auth import verify_admin 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, get_invitation_repository,
)
from baby_monitor.repositories.dependencies.get_user_repository import (
get_user_repository, get_user_repository,
)
from baby_monitor.repositories.dependencies.get_child_repository import (
get_child_repository, get_child_repository,
)
from baby_monitor.repositories.dependencies.get_feeding_repository import (
get_feeding_repository, get_feeding_repository,
)
from baby_monitor.repositories.dependencies.get_diaper_change_repository import (
get_diaper_change_repository, get_diaper_change_repository,
)
from baby_monitor.repositories.dependencies.get_sleep_repository import (
get_sleep_repository, get_sleep_repository,
) )
from baby_monitor.repositories.interfaces import ( from baby_monitor.repositories.interfaces import (
+2 -2
View File
@@ -1,8 +1,8 @@
"""Authentication router for login and user management.""" """Authentication router for login and user management."""
import secrets import secrets
from fastapi import APIRouter, HTTPException, Depends, Header
from typing import Annotated from typing import Annotated
from fastapi import APIRouter, HTTPException, Depends, Header
from pydantic import BaseModel from pydantic import BaseModel
from baby_monitor.models.auth import ( from baby_monitor.models.auth import (
@@ -29,7 +29,7 @@ from baby_monitor.repositories.interfaces import (
DiaperChangeRepositoryInterface, DiaperChangeRepositoryInterface,
SleepRepositoryInterface, 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"]) router = APIRouter(prefix="/api", tags=["authentication"])
+29 -15
View File
@@ -8,14 +8,14 @@ from pydantic import BaseModel
from baby_monitor.models.child import CreateChildRequest, ChildResponse from baby_monitor.models.child import CreateChildRequest, ChildResponse
from baby_monitor.routers.auth import verify_token from baby_monitor.routers.auth import verify_token
from baby_monitor.repositories.child.sqlite_child import SQLiteChildRepository from baby_monitor.repositories.interfaces import (
from baby_monitor.repositories.dependencies.get_child_repository import ( ChildRepositoryInterface,
get_child_repository, 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, get_child_invitation_repository,
) )
from baby_monitor.repositories.interfaces import ChildInvitationRepositoryInterface # noqa: E501
router = APIRouter(prefix="/api/children", tags=["children"]) router = APIRouter(prefix="/api/children", tags=["children"])
@@ -45,7 +45,9 @@ class RedeemChildInvitationRequest(BaseModel):
def create_child( def create_child(
request: CreateChildRequest, request: CreateChildRequest,
user_id: Annotated[int, Depends(verify_token)], user_id: Annotated[int, Depends(verify_token)],
child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], child_repo: Annotated[
ChildRepositoryInterface, Depends(get_child_repository)
],
) -> ChildResponse: ) -> ChildResponse:
"""Create a new child for the authenticated user.""" """Create a new child for the authenticated user."""
child = child_repo.create( child = child_repo.create(
@@ -61,7 +63,9 @@ def create_child(
@router.get("", response_model=list[ChildResponse]) @router.get("", response_model=list[ChildResponse])
def get_user_children( def get_user_children(
user_id: Annotated[int, Depends(verify_token)], 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]: ) -> list[ChildResponse]:
"""Get all children for the authenticated user.""" """Get all children for the authenticated user."""
children = child_repo.get_by_user_id(user_id) children = child_repo.get_by_user_id(user_id)
@@ -72,7 +76,9 @@ def get_user_children(
def get_child( def get_child(
child_id: int, child_id: int,
user_id: Annotated[int, Depends(verify_token)], user_id: Annotated[int, Depends(verify_token)],
child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], child_repo: Annotated[
ChildRepositoryInterface, Depends(get_child_repository)
],
) -> ChildResponse: ) -> ChildResponse:
"""Get a specific child by ID.""" """Get a specific child by ID."""
child = child_repo.get_by_id(child_id) child = child_repo.get_by_id(child_id)
@@ -93,7 +99,9 @@ def update_child(
child_id: int, child_id: int,
request: CreateChildRequest, request: CreateChildRequest,
user_id: Annotated[int, Depends(verify_token)], user_id: Annotated[int, Depends(verify_token)],
child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], child_repo: Annotated[
ChildRepositoryInterface, Depends(get_child_repository)
],
) -> ChildResponse: ) -> ChildResponse:
"""Update a child's information.""" """Update a child's information."""
# First check if child exists and belongs to user # First check if child exists and belongs to user
@@ -124,10 +132,13 @@ def update_child(
def create_child_invitation( def create_child_invitation(
request: CreateChildInvitationRequest, request: CreateChildInvitationRequest,
user_id: Annotated[int, Depends(verify_token)], 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[ invitation_repo: Annotated[
ChildInvitationRepositoryInterface, Depends(get_child_invitation_repository) ChildInvitationRepositoryInterface,
], # noqa: E501 Depends(get_child_invitation_repository),
],
) -> ChildInvitationResponse: ) -> ChildInvitationResponse:
"""Create an invitation code to share child access with another user.""" """Create an invitation code to share child access with another user."""
# Verify the child exists and user has access # Verify the child exists and user has access
@@ -165,10 +176,13 @@ def create_child_invitation(
def redeem_child_invitation( def redeem_child_invitation(
request: RedeemChildInvitationRequest, request: RedeemChildInvitationRequest,
user_id: Annotated[int, Depends(verify_token)], 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[ invitation_repo: Annotated[
ChildInvitationRepositoryInterface, Depends(get_child_invitation_repository) ChildInvitationRepositoryInterface,
], # noqa: E501 Depends(get_child_invitation_repository),
],
) -> dict: ) -> dict:
"""Redeem an invitation code to gain access to a child.""" """Redeem an invitation code to gain access to a child."""
# Verify the invitation code # Verify the invitation code
+22 -16
View File
@@ -9,17 +9,15 @@ from baby_monitor.models.diaper_change import (
DiaperChangeResponse, DiaperChangeResponse,
) )
from baby_monitor.routers.auth import verify_token from baby_monitor.routers.auth import verify_token
from baby_monitor.repositories.diaper_change.sqlite_diaper_change import ( from baby_monitor.repositories.interfaces import (
SQLiteDiaperChangeRepository, DiaperChangeRepositoryInterface,
ChildRepositoryInterface,
) )
from baby_monitor.repositories.dependencies.get_diaper_change_repository import ( # noqa: E501 from baby_monitor.repositories.dependencies import (
get_diaper_change_repository, 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, 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"]) router = APIRouter(prefix="/api/diaper-changes", tags=["diaper-changes"])
@@ -29,9 +27,11 @@ def create_diaper_change(
request: CreateDiaperChangeRequest, request: CreateDiaperChangeRequest,
user_id: Annotated[int, Depends(verify_token)], user_id: Annotated[int, Depends(verify_token)],
diaper_repo: Annotated[ 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: ) -> DiaperChangeResponse:
"""Create a new diaper change log entry.""" """Create a new diaper change log entry."""
# Verify the child belongs to the authenticated user # Verify the child belongs to the authenticated user
@@ -53,7 +53,7 @@ def create_diaper_change(
def get_user_diaper_changes( def get_user_diaper_changes(
user_id: Annotated[int, Depends(verify_token)], user_id: Annotated[int, Depends(verify_token)],
diaper_repo: Annotated[ diaper_repo: Annotated[
SQLiteDiaperChangeRepository, Depends(get_diaper_change_repository) DiaperChangeRepositoryInterface, Depends(get_diaper_change_repository)
], ],
) -> list[DiaperChangeResponse]: ) -> list[DiaperChangeResponse]:
"""Get all diaper change logs for the authenticated user's children.""" """Get all diaper change logs for the authenticated user's children."""
@@ -66,9 +66,11 @@ def get_diaper_change(
diaper_change_id: int, diaper_change_id: int,
user_id: Annotated[int, Depends(verify_token)], user_id: Annotated[int, Depends(verify_token)],
diaper_repo: Annotated[ 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: ) -> DiaperChangeResponse:
"""Get a specific diaper change log by ID.""" """Get a specific diaper change log by ID."""
diaper_change = diaper_repo.get_by_id(diaper_change_id) diaper_change = diaper_repo.get_by_id(diaper_change_id)
@@ -88,9 +90,11 @@ def update_diaper_change(
request: UpdateDiaperChangeRequest, request: UpdateDiaperChangeRequest,
user_id: Annotated[int, Depends(verify_token)], user_id: Annotated[int, Depends(verify_token)],
diaper_repo: Annotated[ 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: ) -> DiaperChangeResponse:
"""Update an existing diaper change log.""" """Update an existing diaper change log."""
diaper_change = diaper_repo.get_by_id(diaper_change_id) diaper_change = diaper_repo.get_by_id(diaper_change_id)
@@ -123,9 +127,11 @@ def delete_diaper_change(
diaper_change_id: int, diaper_change_id: int,
user_id: Annotated[int, Depends(verify_token)], user_id: Annotated[int, Depends(verify_token)],
diaper_repo: Annotated[ 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: ) -> None:
"""Delete a diaper change log.""" """Delete a diaper change log."""
diaper_change = diaper_repo.get_by_id(diaper_change_id) diaper_change = diaper_repo.get_by_id(diaper_change_id)
+42 -19
View File
@@ -9,17 +9,15 @@ from baby_monitor.models.feeding import (
FeedingResponse, FeedingResponse,
) )
from baby_monitor.routers.auth import verify_token from baby_monitor.routers.auth import verify_token
from baby_monitor.repositories.feeding.sqlite_feeding import ( from baby_monitor.repositories.interfaces import (
SQLiteFeedingRepository, ChildRepositoryInterface,
FeedingRepositoryInterface,
) )
from baby_monitor.repositories.dependencies.get_feeding_repository import ( from baby_monitor.repositories.dependencies import (
get_feeding_repository, get_feeding_repository,
)
from baby_monitor.repositories.child.sqlite_child import SQLiteChildRepository
from baby_monitor.repositories.dependencies.get_child_repository import (
get_child_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/feedings", tags=["feedings"]) router = APIRouter(prefix="/api/feedings", tags=["feedings"])
@@ -28,8 +26,12 @@ router = APIRouter(prefix="/api/feedings", tags=["feedings"])
def create_feeding( def create_feeding(
request: CreateFeedingRequest, request: CreateFeedingRequest,
user_id: Annotated[int, Depends(verify_token)], user_id: Annotated[int, Depends(verify_token)],
feeding_repo: Annotated[SQLiteFeedingRepository, Depends(get_feeding_repository)], feeding_repo: Annotated[
child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], FeedingRepositoryInterface, Depends(get_feeding_repository)
],
child_repo: Annotated[
ChildRepositoryInterface, Depends(get_child_repository)
],
) -> FeedingResponse: ) -> FeedingResponse:
"""Create a new feeding log entry.""" """Create a new feeding log entry."""
# Verify the child belongs to the authenticated user # Verify the child belongs to the authenticated user
@@ -48,8 +50,12 @@ def create_feeding(
@router.get("/active", response_model=FeedingResponse | None) @router.get("/active", response_model=FeedingResponse | None)
def get_active_feeding( def get_active_feeding(
user_id: Annotated[int, Depends(verify_token)], user_id: Annotated[int, Depends(verify_token)],
feeding_repo: Annotated[SQLiteFeedingRepository, Depends(get_feeding_repository)], feeding_repo: Annotated[
child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], FeedingRepositoryInterface, Depends(get_feeding_repository)
],
child_repo: Annotated[
ChildRepositoryInterface, Depends(get_child_repository)
],
) -> FeedingResponse | None: ) -> FeedingResponse | None:
"""Get the current active feeding (where end_time is null) for the user.""" """Get the current active feeding (where end_time is null) for the user."""
feedings = feeding_repo.get_by_user_id(user_id) feedings = feeding_repo.get_by_user_id(user_id)
@@ -70,7 +76,9 @@ def get_active_feeding(
@router.get("", response_model=list[FeedingResponse]) @router.get("", response_model=list[FeedingResponse])
def get_user_feedings( def get_user_feedings(
user_id: Annotated[int, Depends(verify_token)], 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]: ) -> list[FeedingResponse]:
"""Get all feeding logs for the authenticated user's children.""" """Get all feeding logs for the authenticated user's children."""
feedings = feeding_repo.get_by_user_id(user_id) feedings = feeding_repo.get_by_user_id(user_id)
@@ -81,8 +89,12 @@ def get_user_feedings(
def get_feeding( def get_feeding(
feeding_id: int, feeding_id: int,
user_id: Annotated[int, Depends(verify_token)], user_id: Annotated[int, Depends(verify_token)],
feeding_repo: Annotated[SQLiteFeedingRepository, Depends(get_feeding_repository)], feeding_repo: Annotated[
child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], FeedingRepositoryInterface, Depends(get_feeding_repository)
],
child_repo: Annotated[
ChildRepositoryInterface, Depends(get_child_repository)
],
) -> FeedingResponse: ) -> FeedingResponse:
"""Get a specific feeding log by ID.""" """Get a specific feeding log by ID."""
feeding = feeding_repo.get_by_id(feeding_id) feeding = feeding_repo.get_by_id(feeding_id)
@@ -101,8 +113,12 @@ def update_feeding(
feeding_id: int, feeding_id: int,
request: UpdateFeedingRequest, request: UpdateFeedingRequest,
user_id: Annotated[int, Depends(verify_token)], user_id: Annotated[int, Depends(verify_token)],
feeding_repo: Annotated[SQLiteFeedingRepository, Depends(get_feeding_repository)], feeding_repo: Annotated[
child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], FeedingRepositoryInterface, Depends(get_feeding_repository)
],
child_repo: Annotated[
ChildRepositoryInterface, Depends(get_child_repository)
],
) -> FeedingResponse: ) -> FeedingResponse:
"""Update a feeding log entry.""" """Update a feeding log entry."""
feeding = feeding_repo.get_by_id(feeding_id) 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) verify_child_access(child_repo, feeding["child_id"], user_id)
# Update the feeding # Update the feeding
feeding_type_value = (
request.feeding_type.value if request.feeding_type else None
)
updated_feeding = feeding_repo.update( updated_feeding = feeding_repo.update(
feeding_id=feeding_id, feeding_id=feeding_id,
start_time=request.start_time, start_time=request.start_time,
end_time=request.end_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: if not updated_feeding:
@@ -131,8 +150,12 @@ def update_feeding(
def delete_feeding( def delete_feeding(
feeding_id: int, feeding_id: int,
user_id: Annotated[int, Depends(verify_token)], user_id: Annotated[int, Depends(verify_token)],
feeding_repo: Annotated[SQLiteFeedingRepository, Depends(get_feeding_repository)], feeding_repo: Annotated[
child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], FeedingRepositoryInterface, Depends(get_feeding_repository)
],
child_repo: Annotated[
ChildRepositoryInterface, Depends(get_child_repository)
],
) -> None: ) -> None:
"""Delete a feeding log entry.""" """Delete a feeding log entry."""
feeding = feeding_repo.get_by_id(feeding_id) feeding = feeding_repo.get_by_id(feeding_id)
+39 -17
View File
@@ -9,15 +9,15 @@ from baby_monitor.models.sleep import (
SleepResponse, SleepResponse,
) )
from baby_monitor.routers.auth import verify_token from baby_monitor.routers.auth import verify_token
from baby_monitor.repositories.sleep.sqlite_sleep import SQLiteSleepRepository from baby_monitor.repositories.interfaces import (
from baby_monitor.repositories.dependencies.get_sleep_repository import ( SleepRepositoryInterface,
get_sleep_repository, ChildRepositoryInterface,
) )
from baby_monitor.repositories.child.sqlite_child import SQLiteChildRepository from baby_monitor.repositories.dependencies import (
from baby_monitor.repositories.dependencies.get_child_repository import ( get_sleep_repository,
get_child_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"]) router = APIRouter(prefix="/api/sleep", tags=["sleep"])
@@ -26,8 +26,12 @@ router = APIRouter(prefix="/api/sleep", tags=["sleep"])
def create_sleep( def create_sleep(
request: CreateSleepRequest, request: CreateSleepRequest,
user_id: Annotated[int, Depends(verify_token)], user_id: Annotated[int, Depends(verify_token)],
sleep_repo: Annotated[SQLiteSleepRepository, Depends(get_sleep_repository)], sleep_repo: Annotated[
child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], SleepRepositoryInterface, Depends(get_sleep_repository)
],
child_repo: Annotated[
ChildRepositoryInterface, Depends(get_child_repository)
],
) -> SleepResponse: ) -> SleepResponse:
"""Create a new sleep log entry.""" """Create a new sleep log entry."""
# Verify the child belongs to the authenticated user # Verify the child belongs to the authenticated user
@@ -44,8 +48,12 @@ def create_sleep(
@router.get("/active", response_model=SleepResponse | None) @router.get("/active", response_model=SleepResponse | None)
def get_active_sleep( def get_active_sleep(
user_id: Annotated[int, Depends(verify_token)], user_id: Annotated[int, Depends(verify_token)],
sleep_repo: Annotated[SQLiteSleepRepository, Depends(get_sleep_repository)], sleep_repo: Annotated[
child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], SleepRepositoryInterface, Depends(get_sleep_repository)
],
child_repo: Annotated[
ChildRepositoryInterface, Depends(get_child_repository)
],
) -> SleepResponse | None: ) -> SleepResponse | None:
"""Get the current active sleep (where end_time is null) for the user.""" """Get the current active sleep (where end_time is null) for the user."""
sleeps = sleep_repo.get_by_user_id(user_id) sleeps = sleep_repo.get_by_user_id(user_id)
@@ -66,7 +74,9 @@ def get_active_sleep(
@router.get("", response_model=list[SleepResponse]) @router.get("", response_model=list[SleepResponse])
def get_user_sleeps( def get_user_sleeps(
user_id: Annotated[int, Depends(verify_token)], 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]: ) -> list[SleepResponse]:
"""Get all sleep logs for the authenticated user's children.""" """Get all sleep logs for the authenticated user's children."""
sleeps = sleep_repo.get_by_user_id(user_id) sleeps = sleep_repo.get_by_user_id(user_id)
@@ -77,8 +87,12 @@ def get_user_sleeps(
def get_sleep( def get_sleep(
sleep_id: int, sleep_id: int,
user_id: Annotated[int, Depends(verify_token)], user_id: Annotated[int, Depends(verify_token)],
sleep_repo: Annotated[SQLiteSleepRepository, Depends(get_sleep_repository)], sleep_repo: Annotated[
child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], SleepRepositoryInterface, Depends(get_sleep_repository)
],
child_repo: Annotated[
ChildRepositoryInterface, Depends(get_child_repository)
],
) -> SleepResponse: ) -> SleepResponse:
"""Get a specific sleep log by ID.""" """Get a specific sleep log by ID."""
sleep = sleep_repo.get_by_id(sleep_id) sleep = sleep_repo.get_by_id(sleep_id)
@@ -97,8 +111,12 @@ def update_sleep(
sleep_id: int, sleep_id: int,
request: UpdateSleepRequest, request: UpdateSleepRequest,
user_id: Annotated[int, Depends(verify_token)], user_id: Annotated[int, Depends(verify_token)],
sleep_repo: Annotated[SQLiteSleepRepository, Depends(get_sleep_repository)], sleep_repo: Annotated[
child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], SleepRepositoryInterface, Depends(get_sleep_repository)
],
child_repo: Annotated[
ChildRepositoryInterface, Depends(get_child_repository)
],
) -> SleepResponse: ) -> SleepResponse:
"""Update an existing sleep log.""" """Update an existing sleep log."""
sleep = sleep_repo.get_by_id(sleep_id) sleep = sleep_repo.get_by_id(sleep_id)
@@ -124,8 +142,12 @@ def update_sleep(
def delete_sleep( def delete_sleep(
sleep_id: int, sleep_id: int,
user_id: Annotated[int, Depends(verify_token)], user_id: Annotated[int, Depends(verify_token)],
sleep_repo: Annotated[SQLiteSleepRepository, Depends(get_sleep_repository)], sleep_repo: Annotated[
child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], SleepRepositoryInterface, Depends(get_sleep_repository)
],
child_repo: Annotated[
ChildRepositoryInterface, Depends(get_child_repository)
],
) -> None: ) -> None:
"""Delete a sleep log.""" """Delete a sleep log."""
sleep = sleep_repo.get_by_id(sleep_id) sleep = sleep_repo.get_by_id(sleep_id)
+10
View File
@@ -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",
]
+17
View File
@@ -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")
@@ -1,11 +1,13 @@
"""Access control utility functions for verifying resource ownership.""" """Definition of verify_child_access function."""
from fastapi import HTTPException from fastapi import HTTPException
from baby_monitor.repositories.child.sqlite_child import SQLiteChildRepository from baby_monitor.repositories.interfaces import (
ChildRepositoryInterface,
)
def verify_child_access( def verify_child_access(
child_repo: SQLiteChildRepository, child_id: int, user_id: int child_repo: ChildRepositoryInterface, child_id: int, user_id: int
) -> None: ) -> None:
"""Verify that user has access to the child. """Verify that user has access to the child.
@@ -1,22 +1,8 @@
"""Password hashing utilities using bcrypt.""" """Definition of verify_password function."""
import bcrypt 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: def verify_password(password: str, hashed_password: str) -> bool:
"""Verify a password against a hashed password. """Verify a password against a hashed password.