feat(backend): refactor mono repository
This commit is contained in:
158
backend/repositories/service_availability.py
Normal file
158
backend/repositories/service_availability.py
Normal file
@@ -0,0 +1,158 @@
|
||||
# myapp/repositories/service_availability.py
|
||||
# ------------------------------------------------------------
|
||||
# Persistence layer for the ServiceAvailability table.
|
||||
# ------------------------------------------------------------
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional, Sequence, Any
|
||||
|
||||
from ..db import BaseRepository
|
||||
from ..models import ServiceAvailability as ServiceAvailabilityModel
|
||||
|
||||
|
||||
class ServiceAvailabilityRepository(BaseRepository[ServiceAvailabilityModel]):
|
||||
"""
|
||||
CRUD + query helpers for the ``ServiceAvailability`` table.
|
||||
|
||||
The table records which members are allowed to receive which
|
||||
service‑type slots (e.g. “9 AM”, “11 AM”, “6 PM”). All SQL is
|
||||
parameterised to stay safe from injection attacks.
|
||||
"""
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Table‑level constants – change them in one place if the schema evolves.
|
||||
# ------------------------------------------------------------------
|
||||
_TABLE = "ServiceAvailability"
|
||||
_PK = "ServiceAvailabilityId"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Basic CRUD helpers
|
||||
# ------------------------------------------------------------------
|
||||
def create(
|
||||
self,
|
||||
member_id: int,
|
||||
service_type_id: int,
|
||||
) -> ServiceAvailabilityModel:
|
||||
"""
|
||||
Insert a new availability row.
|
||||
|
||||
The ``UNIQUE (MemberId, ServiceTypeId)`` constraint guarantees
|
||||
idempotency – if the pair already exists SQLite will raise an
|
||||
``IntegrityError``. To make the operation truly idempotent we
|
||||
first check for an existing row and return it unchanged.
|
||||
"""
|
||||
existing = self.get(member_id, service_type_id)
|
||||
if existing:
|
||||
return existing
|
||||
|
||||
avail = ServiceAvailabilityModel(
|
||||
ServiceAvailabilityId=-1, # placeholder – will be overwritten
|
||||
MemberId=member_id,
|
||||
ServiceTypeId=service_type_id,
|
||||
)
|
||||
return self._insert(self._TABLE, avail, self._PK)
|
||||
|
||||
def get(
|
||||
self,
|
||||
member_id: int,
|
||||
service_type_id: int,
|
||||
) -> Optional[ServiceAvailabilityModel]:
|
||||
"""
|
||||
Retrieve a single availability record for the given member /
|
||||
service‑type pair, or ``None`` if it does not exist.
|
||||
"""
|
||||
sql = f"""
|
||||
SELECT *
|
||||
FROM {self._TABLE}
|
||||
WHERE MemberId = ?
|
||||
AND ServiceTypeId = ?
|
||||
"""
|
||||
row = self.db.fetchone(sql, (member_id, service_type_id))
|
||||
return ServiceAvailabilityModel.from_row(row) if row else None
|
||||
|
||||
def delete(self, availability_id: int) -> None:
|
||||
"""
|
||||
Hard‑delete an availability row by its primary key.
|
||||
Use with care – most callers will prefer ``revoke`` (by member &
|
||||
service type) which is a bit more expressive.
|
||||
"""
|
||||
sql = f"DELETE FROM {self._TABLE} WHERE {self._PK} = ?"
|
||||
self.db.execute(sql, (availability_id,))
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Convenience “grant / revoke” helpers (the most common ops)
|
||||
# ------------------------------------------------------------------
|
||||
def grant(self, member_id: int, service_type_id: int) -> ServiceAvailabilityModel:
|
||||
"""
|
||||
Public API to give a member permission for a particular service slot.
|
||||
Internally delegates to ``create`` which already handles the
|
||||
idempotent‑check.
|
||||
"""
|
||||
return self.create(member_id, service_type_id)
|
||||
|
||||
def revoke(self, member_id: int, service_type_id: int) -> None:
|
||||
"""
|
||||
Remove a member’s permission for a particular service slot.
|
||||
"""
|
||||
sql = f"""
|
||||
DELETE FROM {self._TABLE}
|
||||
WHERE MemberId = ?
|
||||
AND ServiceTypeId = ?
|
||||
"""
|
||||
self.db.execute(sql, (member_id, service_type_id))
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Query helpers used by the scheduling service
|
||||
# ------------------------------------------------------------------
|
||||
def list_by_member(self, member_id: int) -> List[ServiceAvailabilityModel]:
|
||||
"""
|
||||
Return every ``ServiceAvailability`` row that belongs to the given
|
||||
member. Handy for building a member’s personal “available slots”
|
||||
view.
|
||||
"""
|
||||
sql = f"""
|
||||
SELECT *
|
||||
FROM {self._TABLE}
|
||||
WHERE MemberId = ?
|
||||
"""
|
||||
rows = self.db.fetchall(sql, (member_id,))
|
||||
return [ServiceAvailabilityModel.from_row(r) for r in rows]
|
||||
|
||||
def list_by_service_type(self, service_type_id: int) -> List[ServiceAvailabilityModel]:
|
||||
"""
|
||||
Return all members that are allowed to receive the given service type.
|
||||
"""
|
||||
sql = f"""
|
||||
SELECT *
|
||||
FROM {self._TABLE}
|
||||
WHERE ServiceTypeId = ?
|
||||
"""
|
||||
rows = self.db.fetchall(sql, (service_type_id,))
|
||||
return [ServiceAvailabilityModel.from_row(r) for r in rows]
|
||||
|
||||
def list_all(self) -> List[ServiceAvailabilityModel]:
|
||||
"""
|
||||
Return every row in the table – useful for admin dashboards or
|
||||
bulk‑export scripts.
|
||||
"""
|
||||
return self._select_all(self._TABLE, ServiceAvailabilityModel)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Helper for the round‑robin scheduler
|
||||
# ------------------------------------------------------------------
|
||||
def members_for_type(self, service_type_id: int) -> List[int]:
|
||||
"""
|
||||
Return a flat list of ``MemberId`` values that are eligible for the
|
||||
supplied ``service_type_id``. The scheduling service can then
|
||||
intersect this list with the pool of members that have the correct
|
||||
classification, activity flag, etc.
|
||||
"""
|
||||
sql = f"""
|
||||
SELECT MemberId
|
||||
FROM {self._TABLE}
|
||||
WHERE ServiceTypeId = ?
|
||||
"""
|
||||
rows = self.db.fetchall(sql, (service_type_id,))
|
||||
# ``rows`` is a sequence of sqlite3.Row objects; each row acts like a dict.
|
||||
return [row["MemberId"] for row in rows]
|
||||
Reference in New Issue
Block a user