Initial commit
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
"""Abstract repository contracts.
|
||||
|
||||
`Repository[T]` is the plain CRUD base. Use it for simple entities like
|
||||
VacationDays that have no GDPR deletion requirements.
|
||||
|
||||
`SoftDeleteRepository[T]` extends it for entities that need an audit trail
|
||||
(soft-delete) plus a GDPR hard delete. Use it for PersonalData and any other
|
||||
entity that directly holds PII. The `list()` contract on soft-deletable
|
||||
entities should always filter `WHERE deleted_at IS NULL` by default.
|
||||
|
||||
Dependency direction: routers/services depend on these interfaces only —
|
||||
never on a concrete DB class.
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Generic, TypeVar
|
||||
from uuid import UUID
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class Repository(ABC, Generic[T]):
|
||||
@abstractmethod
|
||||
async def create(self, item: T) -> T:
|
||||
"""Persist a new item and return it."""
|
||||
|
||||
@abstractmethod
|
||||
async def get_by_id(self, id: UUID) -> T | None:
|
||||
"""Return the item or None if it does not exist."""
|
||||
|
||||
@abstractmethod
|
||||
async def update(self, id: UUID, item: T) -> T:
|
||||
"""Update an existing item. Raise NotFoundError if missing."""
|
||||
|
||||
@abstractmethod
|
||||
async def delete(self, id: UUID) -> None:
|
||||
"""Remove an item. Raise NotFoundError if missing."""
|
||||
|
||||
@abstractmethod
|
||||
async def list(self, **filters: object) -> list[T]:
|
||||
"""Return items matching the given filters."""
|
||||
|
||||
|
||||
class SoftDeleteRepository(Repository[T], Generic[T]):
|
||||
"""Extended contract for GDPR-sensitive entities.
|
||||
|
||||
`delete()` on these entities sets `deleted_at` (soft delete for audit trail);
|
||||
`hard_delete()` removes the row entirely (GDPR erasure). Implementations of
|
||||
`list()` must filter `WHERE deleted_at IS NULL` unless `include_deleted=True`
|
||||
is passed.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
async def soft_delete(self, id: UUID) -> None:
|
||||
"""Set deleted_at to now. Raise NotFoundError if missing."""
|
||||
|
||||
@abstractmethod
|
||||
async def hard_delete(self, id: UUID) -> None:
|
||||
"""Permanently remove the row (GDPR erasure). Raise NotFoundError if missing."""
|
||||
|
||||
async def delete(self, id: UUID) -> None:
|
||||
"""Default: calls soft_delete. Override only if hard delete is the right default."""
|
||||
await self.soft_delete(id)
|
||||
@@ -0,0 +1,17 @@
|
||||
"""Abstract repository interface for BreakPeriod.
|
||||
|
||||
Adds entity-specific queries on top of the generic CRUD contract. Concrete
|
||||
implementations live in app/repositories_impl/<backend>/.
|
||||
"""
|
||||
|
||||
from abc import abstractmethod
|
||||
from uuid import UUID
|
||||
|
||||
from app.entities.break_period import BreakPeriod
|
||||
from app.repositories.base import Repository
|
||||
|
||||
|
||||
class BreakPeriodRepository(Repository[BreakPeriod]):
|
||||
@abstractmethod
|
||||
async def get_by_workspace(self, workspace_id: UUID) -> list[BreakPeriod]:
|
||||
"""Return all break periods for a workspace."""
|
||||
@@ -0,0 +1,13 @@
|
||||
"""Abstract repository for Child entities."""
|
||||
|
||||
from abc import abstractmethod
|
||||
from uuid import UUID
|
||||
|
||||
from app.entities.child import Child
|
||||
from app.repositories.base import Repository
|
||||
|
||||
|
||||
class ChildRepository(Repository[Child]):
|
||||
@abstractmethod
|
||||
async def list_by_workspace(self, workspace_id: UUID) -> list[Child]:
|
||||
"""Return all children in a workspace."""
|
||||
@@ -0,0 +1,21 @@
|
||||
"""Abstract repository for ParentalLeaveFetch snapshots."""
|
||||
|
||||
from abc import abstractmethod
|
||||
from uuid import UUID
|
||||
|
||||
from app.entities.parental_leave import ParentalLeaveFetch
|
||||
from app.repositories.base import Repository
|
||||
|
||||
|
||||
class ParentalLeaveRepository(Repository[ParentalLeaveFetch]):
|
||||
@abstractmethod
|
||||
async def get_latest_by_parent(
|
||||
self, personal_data_uuid: UUID, child_id: UUID
|
||||
) -> ParentalLeaveFetch | None:
|
||||
"""Return the most recently fetched snapshot for a parent+child pair."""
|
||||
|
||||
@abstractmethod
|
||||
async def list_history(
|
||||
self, personal_data_uuid: UUID, child_id: UUID
|
||||
) -> list[ParentalLeaveFetch]:
|
||||
"""Return all snapshots for a parent+child pair, newest first."""
|
||||
@@ -0,0 +1,17 @@
|
||||
"""Abstract repository for PersonalData.
|
||||
|
||||
Extends SoftDeleteRepository because PersonalData directly holds PII and must
|
||||
support GDPR erasure.
|
||||
"""
|
||||
|
||||
from abc import abstractmethod
|
||||
from uuid import UUID
|
||||
|
||||
from app.entities.personal_data import PersonalData
|
||||
from app.repositories.base import SoftDeleteRepository
|
||||
|
||||
|
||||
class PersonalDataRepository(SoftDeleteRepository[PersonalData]):
|
||||
@abstractmethod
|
||||
async def list_by_workspace(self, workspace_id: UUID) -> list[PersonalData]:
|
||||
"""Return all non-deleted profiles in a workspace (up to 2)."""
|
||||
@@ -0,0 +1,35 @@
|
||||
"""Abstract repository for PlannedLeaveDay calendar events."""
|
||||
|
||||
from abc import abstractmethod
|
||||
from datetime import date
|
||||
from uuid import UUID
|
||||
|
||||
from app.entities.planned_leave import PlannedLeaveDay
|
||||
from app.repositories.base import Repository
|
||||
|
||||
|
||||
class PlannedLeaveRepository(Repository[PlannedLeaveDay]):
|
||||
@abstractmethod
|
||||
async def list_by_workspace_child(
|
||||
self,
|
||||
workspace_id: UUID,
|
||||
child_id: UUID,
|
||||
from_date: date | None = None,
|
||||
to_date: date | None = None,
|
||||
) -> list[PlannedLeaveDay]:
|
||||
"""Return planned days for a workspace+child, optionally within a date range."""
|
||||
|
||||
@abstractmethod
|
||||
async def count_by_parent_type(
|
||||
self,
|
||||
personal_data_uuid: UUID,
|
||||
child_id: UUID,
|
||||
day_type: str,
|
||||
from_date: date | None = None,
|
||||
to_date: date | None = None,
|
||||
) -> int:
|
||||
"""Count planned days of a given type for a parent+child pair."""
|
||||
|
||||
@abstractmethod
|
||||
async def count_dubbeldagar(self, child_id: UUID, from_date: date | None = None) -> int:
|
||||
"""Count dubbeldagar already planned for a child (after from_date)."""
|
||||
@@ -0,0 +1,12 @@
|
||||
"""Abstract repository for User (login accounts)."""
|
||||
|
||||
from abc import abstractmethod
|
||||
|
||||
from app.entities.user import User
|
||||
from app.repositories.base import Repository
|
||||
|
||||
|
||||
class UserRepository(Repository[User]):
|
||||
@abstractmethod
|
||||
async def get_by_email(self, email: str) -> User | None:
|
||||
"""Return the user with this email, or None if not found."""
|
||||
@@ -0,0 +1,17 @@
|
||||
"""Abstract repository interface for VacationDays.
|
||||
|
||||
Adds entity-specific queries on top of the generic CRUD contract. Concrete
|
||||
implementations live in app/repositories_impl/<backend>/.
|
||||
"""
|
||||
|
||||
from abc import abstractmethod
|
||||
from uuid import UUID
|
||||
|
||||
from app.entities.vacation_days import VacationDays
|
||||
from app.repositories.base import Repository
|
||||
|
||||
|
||||
class VacationDaysRepository(Repository[VacationDays]):
|
||||
@abstractmethod
|
||||
async def get_by_parent_year(self, personal_data_uuid: UUID, year: int) -> VacationDays | None:
|
||||
"""Return the entry for a given parent + year (UNIQUE), or None."""
|
||||
@@ -0,0 +1,24 @@
|
||||
"""Abstract repository for Workspace and WorkspaceInvitation."""
|
||||
|
||||
from abc import abstractmethod
|
||||
from uuid import UUID
|
||||
|
||||
from app.entities.workspace import Workspace
|
||||
from app.entities.workspace_invitation import WorkspaceInvitation
|
||||
from app.repositories.base import Repository
|
||||
|
||||
|
||||
class WorkspaceRepository(Repository[Workspace]):
|
||||
pass # CRUD is sufficient; no entity-specific queries needed yet
|
||||
|
||||
|
||||
class WorkspaceInvitationRepository(Repository[WorkspaceInvitation]):
|
||||
@abstractmethod
|
||||
async def get_by_token_hash(self, token_hash: str) -> WorkspaceInvitation | None:
|
||||
"""Look up a pending invitation by the hash of the raw token."""
|
||||
|
||||
@abstractmethod
|
||||
async def list_by_workspace(
|
||||
self, workspace_id: UUID, status: str | None = None
|
||||
) -> list[WorkspaceInvitation]:
|
||||
"""Return invitations for a workspace, optionally filtered by status."""
|
||||
Reference in New Issue
Block a user