Loading .gitignore +2 −2 Changes for .gitignore: 2 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -34,5 +34,5 @@ coverage.xml # Runtime scratch files .tmp/ # Editor hook logs .cursor/hook-logs/ # Cursor editor config .cursor/ src/open_exposure_gateway/adapters/database/mappers.py +36 −0 Changes for src/open_exposure_gateway/adapters/database/mappers.py: 36 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -10,6 +10,7 @@ from open_exposure_gateway.adapters.database.sql import ( CallbackRegistrationRow, OperationRow, QodSessionRow, TrafficInfluenceRow, ) from open_exposure_gateway.domain.models import ( AppDeployment, Loading @@ -20,6 +21,7 @@ from open_exposure_gateway.domain.models import ( CallbackRegistration, Operation, QodSession, TrafficInfluence, ) from open_exposure_gateway.domain.models.qod_sessions.models import QodStatusInfo Loading Loading @@ -186,6 +188,40 @@ class QodSessionMapper: ) class TrafficInfluenceMapper: @staticmethod def to_domain(row: TrafficInfluenceRow) -> TrafficInfluence: return TrafficInfluence( traffic_influence_id=row.traffic_influence_id, operation_id=row.operation_id, app_registration_id=row.app_registration_id, edge_cloud_zone_id=row.edge_cloud_zone_id, edge_cloud_region=row.edge_cloud_region, source_port=row.source_port, destination_port=row.destination_port, destination_protocol=row.destination_protocol, state=row.state, external_ref=row.external_ref, created_at=row.created_at, updated_at=row.updated_at, ) @staticmethod def to_row(domain: TrafficInfluence) -> TrafficInfluenceRow: return TrafficInfluenceRow( traffic_influence_id=domain.traffic_influence_id, operation_id=domain.operation_id, app_registration_id=domain.app_registration_id, edge_cloud_zone_id=domain.edge_cloud_zone_id, edge_cloud_region=domain.edge_cloud_region, source_port=domain.source_port, destination_port=domain.destination_port, destination_protocol=domain.destination_protocol, state=domain.state, external_ref=domain.external_ref, ) class CallbackRegistrationMapper: @staticmethod def to_domain(row: CallbackRegistrationRow) -> CallbackRegistration: Loading src/open_exposure_gateway/adapters/database/repos/app_instances.py +10 −0 Changes for src/open_exposure_gateway/adapters/database/repos/app_instances.py: 10 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -52,6 +52,16 @@ class SqlAppInstanceRepository(AppInstanceRepository): row = await self._session.scalar(stmt.limit(1)) return row is not None async def list_ready_ids_in_zone( self, app_registration_id: UUID, edge_cloud_zone_id: UUID ) -> list[UUID]: stmt = select(AppInstanceRow.app_instance_id).where( AppInstanceRow.app_registration_id == app_registration_id, AppInstanceRow.edge_cloud_zone_id == edge_cloud_zone_id, AppInstanceRow.state == AppInstanceState.READY, ) return list(await self._session.scalars(stmt)) async def save(self, app_instance: AppInstance) -> AppInstance: merged = await self._session.merge(AppInstanceMapper.to_row(app_instance)) await self._session.flush() Loading src/open_exposure_gateway/adapters/database/repos/traffic_influences.py 0 → 100644 +46 −0 Changes for src/open_exposure_gateway/adapters/database/repos/traffic_influences.py: 46 added lines, 0 removed lines. Original line number Diff line number Diff line from uuid import UUID from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from open_exposure_gateway.adapters.database.mappers import TrafficInfluenceMapper from open_exposure_gateway.adapters.database.sql import TrafficInfluenceRow from open_exposure_gateway.domain.models import TrafficInfluence from open_exposure_gateway.domain.models.traffic_influences.enums import TrafficInfluenceState from open_exposure_gateway.ports.database.traffic_influences import TrafficInfluenceRepository class SqlTrafficInfluenceRepository(TrafficInfluenceRepository): def __init__(self, session: AsyncSession) -> None: self._session = session async def get_by_id(self, traffic_influence_id: UUID) -> TrafficInfluence | None: stmt = select(TrafficInfluenceRow).where( TrafficInfluenceRow.traffic_influence_id == traffic_influence_id ) row = await self._session.scalar(stmt) return TrafficInfluenceMapper.to_domain(row) if row is not None else None async def get_by_operation_id(self, operation_id: UUID) -> TrafficInfluence | None: stmt = select(TrafficInfluenceRow).where(TrafficInfluenceRow.operation_id == operation_id) row = await self._session.scalar(stmt) return TrafficInfluenceMapper.to_domain(row) if row is not None else None async def list_by_app_registration_id( self, app_registration_id: UUID | None ) -> list[TrafficInfluence]: stmt = select(TrafficInfluenceRow).where( TrafficInfluenceRow.state != TrafficInfluenceState.DELETED ) if app_registration_id is not None: stmt = stmt.where(TrafficInfluenceRow.app_registration_id == app_registration_id) rows = await self._session.scalars(stmt) return [TrafficInfluenceMapper.to_domain(row) for row in rows] async def save(self, traffic_influence: TrafficInfluence) -> TrafficInfluence: merged = await self._session.merge(TrafficInfluenceMapper.to_row(traffic_influence)) await self._session.flush() saved = await self.get_by_id(merged.traffic_influence_id) if saved is None: raise RuntimeError("Saved traffic influence could not be reloaded") return saved src/open_exposure_gateway/adapters/database/sql.py +26 −0 Changes for src/open_exposure_gateway/adapters/database/sql.py: 26 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ from open_exposure_gateway.domain.models.registration.enums import ( AppRegistrationStatus, PackageType, ) from open_exposure_gateway.domain.models.traffic_influences.enums import TrafficInfluenceState def get_metadata() -> MetaData: Loading Loading @@ -248,3 +249,28 @@ class QodSessionRow(AuditedMixin, Base): started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) ended_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) status_info: Mapped[str | None] = mapped_column(String(32)) class TrafficInfluenceRow(AuditedMixin, Base): __tablename__ = "traffic_influences" __table_args__ = ( Index("idx_traffic_influences_operation", "operation_id"), Index("idx_traffic_influences_app_registration", "app_registration_id"), ) traffic_influence_id: Mapped[UUID] = mapped_column(PG_UUID(as_uuid=True), primary_key=True) operation_id: Mapped[UUID] = mapped_column( ForeignKey("operations.operation_id"), nullable=False ) app_registration_id: Mapped[UUID] = mapped_column( ForeignKey("app_registrations.app_registration_id"), nullable=False ) edge_cloud_zone_id: Mapped[UUID | None] = mapped_column(PG_UUID(as_uuid=True)) edge_cloud_region: Mapped[str | None] = mapped_column(String(256)) source_port: Mapped[int | None] = mapped_column(Integer) destination_port: Mapped[int | None] = mapped_column(Integer) destination_protocol: Mapped[str | None] = mapped_column(String(32)) state: Mapped[TrafficInfluenceState] = mapped_column( _enum_type(TrafficInfluenceState), nullable=False ) external_ref: Mapped[str | None] = mapped_column(String(255)) Loading
.gitignore +2 −2 Changes for .gitignore: 2 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -34,5 +34,5 @@ coverage.xml # Runtime scratch files .tmp/ # Editor hook logs .cursor/hook-logs/ # Cursor editor config .cursor/
src/open_exposure_gateway/adapters/database/mappers.py +36 −0 Changes for src/open_exposure_gateway/adapters/database/mappers.py: 36 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -10,6 +10,7 @@ from open_exposure_gateway.adapters.database.sql import ( CallbackRegistrationRow, OperationRow, QodSessionRow, TrafficInfluenceRow, ) from open_exposure_gateway.domain.models import ( AppDeployment, Loading @@ -20,6 +21,7 @@ from open_exposure_gateway.domain.models import ( CallbackRegistration, Operation, QodSession, TrafficInfluence, ) from open_exposure_gateway.domain.models.qod_sessions.models import QodStatusInfo Loading Loading @@ -186,6 +188,40 @@ class QodSessionMapper: ) class TrafficInfluenceMapper: @staticmethod def to_domain(row: TrafficInfluenceRow) -> TrafficInfluence: return TrafficInfluence( traffic_influence_id=row.traffic_influence_id, operation_id=row.operation_id, app_registration_id=row.app_registration_id, edge_cloud_zone_id=row.edge_cloud_zone_id, edge_cloud_region=row.edge_cloud_region, source_port=row.source_port, destination_port=row.destination_port, destination_protocol=row.destination_protocol, state=row.state, external_ref=row.external_ref, created_at=row.created_at, updated_at=row.updated_at, ) @staticmethod def to_row(domain: TrafficInfluence) -> TrafficInfluenceRow: return TrafficInfluenceRow( traffic_influence_id=domain.traffic_influence_id, operation_id=domain.operation_id, app_registration_id=domain.app_registration_id, edge_cloud_zone_id=domain.edge_cloud_zone_id, edge_cloud_region=domain.edge_cloud_region, source_port=domain.source_port, destination_port=domain.destination_port, destination_protocol=domain.destination_protocol, state=domain.state, external_ref=domain.external_ref, ) class CallbackRegistrationMapper: @staticmethod def to_domain(row: CallbackRegistrationRow) -> CallbackRegistration: Loading
src/open_exposure_gateway/adapters/database/repos/app_instances.py +10 −0 Changes for src/open_exposure_gateway/adapters/database/repos/app_instances.py: 10 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -52,6 +52,16 @@ class SqlAppInstanceRepository(AppInstanceRepository): row = await self._session.scalar(stmt.limit(1)) return row is not None async def list_ready_ids_in_zone( self, app_registration_id: UUID, edge_cloud_zone_id: UUID ) -> list[UUID]: stmt = select(AppInstanceRow.app_instance_id).where( AppInstanceRow.app_registration_id == app_registration_id, AppInstanceRow.edge_cloud_zone_id == edge_cloud_zone_id, AppInstanceRow.state == AppInstanceState.READY, ) return list(await self._session.scalars(stmt)) async def save(self, app_instance: AppInstance) -> AppInstance: merged = await self._session.merge(AppInstanceMapper.to_row(app_instance)) await self._session.flush() Loading
src/open_exposure_gateway/adapters/database/repos/traffic_influences.py 0 → 100644 +46 −0 Changes for src/open_exposure_gateway/adapters/database/repos/traffic_influences.py: 46 added lines, 0 removed lines. Original line number Diff line number Diff line from uuid import UUID from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from open_exposure_gateway.adapters.database.mappers import TrafficInfluenceMapper from open_exposure_gateway.adapters.database.sql import TrafficInfluenceRow from open_exposure_gateway.domain.models import TrafficInfluence from open_exposure_gateway.domain.models.traffic_influences.enums import TrafficInfluenceState from open_exposure_gateway.ports.database.traffic_influences import TrafficInfluenceRepository class SqlTrafficInfluenceRepository(TrafficInfluenceRepository): def __init__(self, session: AsyncSession) -> None: self._session = session async def get_by_id(self, traffic_influence_id: UUID) -> TrafficInfluence | None: stmt = select(TrafficInfluenceRow).where( TrafficInfluenceRow.traffic_influence_id == traffic_influence_id ) row = await self._session.scalar(stmt) return TrafficInfluenceMapper.to_domain(row) if row is not None else None async def get_by_operation_id(self, operation_id: UUID) -> TrafficInfluence | None: stmt = select(TrafficInfluenceRow).where(TrafficInfluenceRow.operation_id == operation_id) row = await self._session.scalar(stmt) return TrafficInfluenceMapper.to_domain(row) if row is not None else None async def list_by_app_registration_id( self, app_registration_id: UUID | None ) -> list[TrafficInfluence]: stmt = select(TrafficInfluenceRow).where( TrafficInfluenceRow.state != TrafficInfluenceState.DELETED ) if app_registration_id is not None: stmt = stmt.where(TrafficInfluenceRow.app_registration_id == app_registration_id) rows = await self._session.scalars(stmt) return [TrafficInfluenceMapper.to_domain(row) for row in rows] async def save(self, traffic_influence: TrafficInfluence) -> TrafficInfluence: merged = await self._session.merge(TrafficInfluenceMapper.to_row(traffic_influence)) await self._session.flush() saved = await self.get_by_id(merged.traffic_influence_id) if saved is None: raise RuntimeError("Saved traffic influence could not be reloaded") return saved
src/open_exposure_gateway/adapters/database/sql.py +26 −0 Changes for src/open_exposure_gateway/adapters/database/sql.py: 26 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ from open_exposure_gateway.domain.models.registration.enums import ( AppRegistrationStatus, PackageType, ) from open_exposure_gateway.domain.models.traffic_influences.enums import TrafficInfluenceState def get_metadata() -> MetaData: Loading Loading @@ -248,3 +249,28 @@ class QodSessionRow(AuditedMixin, Base): started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) ended_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) status_info: Mapped[str | None] = mapped_column(String(32)) class TrafficInfluenceRow(AuditedMixin, Base): __tablename__ = "traffic_influences" __table_args__ = ( Index("idx_traffic_influences_operation", "operation_id"), Index("idx_traffic_influences_app_registration", "app_registration_id"), ) traffic_influence_id: Mapped[UUID] = mapped_column(PG_UUID(as_uuid=True), primary_key=True) operation_id: Mapped[UUID] = mapped_column( ForeignKey("operations.operation_id"), nullable=False ) app_registration_id: Mapped[UUID] = mapped_column( ForeignKey("app_registrations.app_registration_id"), nullable=False ) edge_cloud_zone_id: Mapped[UUID | None] = mapped_column(PG_UUID(as_uuid=True)) edge_cloud_region: Mapped[str | None] = mapped_column(String(256)) source_port: Mapped[int | None] = mapped_column(Integer) destination_port: Mapped[int | None] = mapped_column(Integer) destination_protocol: Mapped[str | None] = mapped_column(String(32)) state: Mapped[TrafficInfluenceState] = mapped_column( _enum_type(TrafficInfluenceState), nullable=False ) external_ref: Mapped[str | None] = mapped_column(String(255))