feat(api): Pydantic schemas + Data Repositories

This commit is contained in:
2025-12-07 12:00:00 +00:00
parent 359291eec7
commit 3170f10e86
23 changed files with 3549 additions and 3 deletions

View File

@@ -0,0 +1,161 @@
"""Incident repository for database operations."""
from datetime import datetime
from typing import Any
from uuid import UUID
import asyncpg
class IncidentRepository:
"""Database operations for incidents."""
def __init__(self, conn: asyncpg.Connection) -> None:
self.conn = conn
async def create(
self,
incident_id: UUID,
org_id: UUID,
service_id: UUID,
title: str,
description: str | None,
severity: str,
) -> dict:
"""Create a new incident."""
row = await self.conn.fetchrow(
"""
INSERT INTO incidents (id, org_id, service_id, title, description, status, severity)
VALUES ($1, $2, $3, $4, $5, 'triggered', $6)
RETURNING id, org_id, service_id, title, description, status, severity,
version, created_at, updated_at
""",
incident_id,
org_id,
service_id,
title,
description,
severity,
)
return dict(row)
async def get_by_id(self, incident_id: UUID) -> dict | None:
"""Get incident by ID."""
row = await self.conn.fetchrow(
"""
SELECT id, org_id, service_id, title, description, status, severity,
version, created_at, updated_at
FROM incidents
WHERE id = $1
""",
incident_id,
)
return dict(row) if row else None
async def get_by_org(
self,
org_id: UUID,
status: str | None = None,
cursor: datetime | None = None,
limit: int = 20,
) -> list[dict]:
"""Get incidents for an organization with optional filtering and pagination."""
query = """
SELECT id, org_id, service_id, title, description, status, severity,
version, created_at, updated_at
FROM incidents
WHERE org_id = $1
"""
params: list[Any] = [org_id]
param_idx = 2
if status:
query += f" AND status = ${param_idx}"
params.append(status)
param_idx += 1
if cursor:
query += f" AND created_at < ${param_idx}"
params.append(cursor)
param_idx += 1
query += f" ORDER BY created_at DESC LIMIT ${param_idx}"
params.append(limit + 1) # Fetch one extra to check if there are more
rows = await self.conn.fetch(query, *params)
return [dict(row) for row in rows]
async def update_status(
self,
incident_id: UUID,
new_status: str,
expected_version: int,
) -> dict | None:
"""Update incident status with optimistic locking.
Returns updated incident if successful, None if version mismatch.
"""
row = await self.conn.fetchrow(
"""
UPDATE incidents
SET status = $2, version = version + 1, updated_at = now()
WHERE id = $1 AND version = $3
RETURNING id, org_id, service_id, title, description, status, severity,
version, created_at, updated_at
""",
incident_id,
new_status,
expected_version,
)
return dict(row) if row else None
async def add_event(
self,
event_id: UUID,
incident_id: UUID,
event_type: str,
actor_user_id: UUID | None,
payload: dict[str, Any] | None,
) -> dict:
"""Add an event to the incident timeline."""
import json
row = await self.conn.fetchrow(
"""
INSERT INTO incident_events (id, incident_id, event_type, actor_user_id, payload)
VALUES ($1, $2, $3, $4, $5)
RETURNING id, incident_id, event_type, actor_user_id, payload, created_at
""",
event_id,
incident_id,
event_type,
actor_user_id,
json.dumps(payload) if payload else None,
)
result = dict(row)
# Parse JSON payload back to dict
if result["payload"]:
result["payload"] = json.loads(result["payload"])
return result
async def get_events(self, incident_id: UUID) -> list[dict]:
"""Get all events for an incident."""
import json
rows = await self.conn.fetch(
"""
SELECT id, incident_id, event_type, actor_user_id, payload, created_at
FROM incident_events
WHERE incident_id = $1
ORDER BY created_at
""",
incident_id,
)
results = []
for row in rows:
result = dict(row)
if result["payload"]:
result["payload"] = json.loads(result["payload"])
results.append(result)
return results