"""Notification repository for database operations.""" from datetime import datetime from uuid import UUID import asyncpg class NotificationRepository: """Database operations for notification targets and attempts.""" def __init__(self, conn: asyncpg.Connection) -> None: self.conn = conn async def create_target( self, target_id: UUID, org_id: UUID, name: str, target_type: str, webhook_url: str | None = None, enabled: bool = True, ) -> dict: """Create a new notification target.""" row = await self.conn.fetchrow( """ INSERT INTO notification_targets (id, org_id, name, target_type, webhook_url, enabled) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, org_id, name, target_type, webhook_url, enabled, created_at """, target_id, org_id, name, target_type, webhook_url, enabled, ) return dict(row) async def get_target_by_id(self, target_id: UUID) -> dict | None: """Get notification target by ID.""" row = await self.conn.fetchrow( """ SELECT id, org_id, name, target_type, webhook_url, enabled, created_at FROM notification_targets WHERE id = $1 """, target_id, ) return dict(row) if row else None async def get_targets_by_org( self, org_id: UUID, enabled_only: bool = False, ) -> list[dict]: """Get all notification targets for an organization.""" query = """ SELECT id, org_id, name, target_type, webhook_url, enabled, created_at FROM notification_targets WHERE org_id = $1 """ if enabled_only: query += " AND enabled = true" query += " ORDER BY name" rows = await self.conn.fetch(query, org_id) return [dict(row) for row in rows] async def update_target( self, target_id: UUID, name: str | None = None, webhook_url: str | None = None, enabled: bool | None = None, ) -> dict | None: """Update a notification target.""" updates = [] params = [target_id] param_idx = 2 if name is not None: updates.append(f"name = ${param_idx}") params.append(name) param_idx += 1 if webhook_url is not None: updates.append(f"webhook_url = ${param_idx}") params.append(webhook_url) param_idx += 1 if enabled is not None: updates.append(f"enabled = ${param_idx}") params.append(enabled) param_idx += 1 if not updates: return await self.get_target_by_id(target_id) query = f""" UPDATE notification_targets SET {", ".join(updates)} WHERE id = $1 RETURNING id, org_id, name, target_type, webhook_url, enabled, created_at """ row = await self.conn.fetchrow(query, *params) return dict(row) if row else None async def delete_target(self, target_id: UUID) -> bool: """Delete a notification target. Returns True if deleted.""" result = await self.conn.execute( "DELETE FROM notification_targets WHERE id = $1", target_id, ) return result == "DELETE 1" async def create_attempt( self, attempt_id: UUID, incident_id: UUID, target_id: UUID, ) -> dict: """Create a notification attempt (idempotent via unique constraint).""" row = await self.conn.fetchrow( """ INSERT INTO notification_attempts (id, incident_id, target_id, status) VALUES ($1, $2, $3, 'pending') ON CONFLICT (incident_id, target_id) DO UPDATE SET id = notification_attempts.id RETURNING id, incident_id, target_id, status, error, sent_at, created_at """, attempt_id, incident_id, target_id, ) return dict(row) async def get_attempt(self, incident_id: UUID, target_id: UUID) -> dict | None: """Get notification attempt for incident and target.""" row = await self.conn.fetchrow( """ SELECT id, incident_id, target_id, status, error, sent_at, created_at FROM notification_attempts WHERE incident_id = $1 AND target_id = $2 """, incident_id, target_id, ) return dict(row) if row else None async def update_attempt_success( self, attempt_id: UUID, sent_at: datetime, ) -> dict | None: """Mark notification attempt as successful.""" row = await self.conn.fetchrow( """ UPDATE notification_attempts SET status = 'sent', sent_at = $2, error = NULL WHERE id = $1 RETURNING id, incident_id, target_id, status, error, sent_at, created_at """, attempt_id, sent_at, ) return dict(row) if row else None async def update_attempt_failure( self, attempt_id: UUID, error: str, ) -> dict | None: """Mark notification attempt as failed.""" row = await self.conn.fetchrow( """ UPDATE notification_attempts SET status = 'failed', error = $2 WHERE id = $1 RETURNING id, incident_id, target_id, status, error, sent_at, created_at """, attempt_id, error, ) return dict(row) if row else None async def get_pending_attempts(self, incident_id: UUID) -> list[dict]: """Get all pending notification attempts for an incident.""" rows = await self.conn.fetch( """ SELECT na.id, na.incident_id, na.target_id, na.status, na.error, na.sent_at, na.created_at, nt.target_type, nt.webhook_url, nt.name as target_name FROM notification_attempts na JOIN notification_targets nt ON nt.id = na.target_id WHERE na.incident_id = $1 AND na.status = 'pending' """, incident_id, ) return [dict(row) for row in rows]