diff --git a/backend/app/api/routes/auth.py b/backend/app/api/routes/auth.py
index ac141421..4b17c162 100644
--- a/backend/app/api/routes/auth.py
+++ b/backend/app/api/routes/auth.py
@@ -5,18 +5,16 @@
from dishka.integrations.fastapi import DishkaRoute
from fastapi import APIRouter, Depends, HTTPException, Request, Response
from fastapi.security import OAuth2PasswordRequestForm
-from pymongo.errors import DuplicateKeyError
from app.core.security import SecurityService
from app.core.utils import get_client_ip
from app.db.repositories import UserRepository
-from app.domain.exceptions import ConflictError
+from app.domain.enums import UserRole
from app.domain.user import DomainUserCreate
from app.schemas_pydantic.common import ErrorResponse
from app.schemas_pydantic.user import (
LoginResponse,
MessageResponse,
- TokenValidationResponse,
UserCreate,
UserResponse,
)
@@ -124,7 +122,7 @@ async def login(
return LoginResponse(
message="Login successful",
username=user.username,
- role="admin" if user.is_superuser else "user",
+ role=user.role,
csrf_token=csrf_token,
)
@@ -134,7 +132,7 @@ async def login(
response_model=UserResponse,
responses={
400: {"model": ErrorResponse, "description": "Username already registered"},
- 409: {"model": ErrorResponse, "description": "Email already registered"},
+ 409: {"model": ErrorResponse, "description": "User already exists"},
},
)
async def register(
@@ -167,26 +165,16 @@ async def register(
)
raise HTTPException(status_code=400, detail="Username already registered")
- try:
- hashed_password = security_service.get_password_hash(user.password)
- create_data = DomainUserCreate(
- username=user.username,
- email=str(user.email),
- hashed_password=hashed_password,
- role=user.role,
- is_active=True,
- is_superuser=False,
- )
- created_user = await user_repo.create_user(create_data)
- except DuplicateKeyError as e:
- logger.warning(
- "Registration failed - duplicate email",
- extra={
- "username": user.username,
- "client_ip": get_client_ip(request),
- },
- )
- raise ConflictError("Email already registered") from e
+ hashed_password = security_service.get_password_hash(user.password)
+ create_data = DomainUserCreate(
+ username=user.username,
+ email=user.email,
+ hashed_password=hashed_password,
+ role=UserRole.USER,
+ is_active=True,
+ is_superuser=False,
+ )
+ created_user = await user_repo.create_user(create_data)
logger.info(
"Registration successful",
@@ -197,15 +185,7 @@ async def register(
},
)
- return UserResponse(
- user_id=created_user.user_id,
- username=created_user.username,
- email=created_user.email,
- role=created_user.role,
- is_superuser=created_user.is_superuser,
- created_at=created_user.created_at,
- updated_at=created_user.updated_at,
- )
+ return UserResponse.model_validate(created_user, from_attributes=True)
@router.get("/me", response_model=UserResponse)
@@ -234,46 +214,6 @@ async def get_current_user_profile(
return UserResponse.model_validate(current_user, from_attributes=True)
-@router.get(
- "/verify-token",
- response_model=TokenValidationResponse,
- responses={401: {"model": ErrorResponse, "description": "Missing or invalid access token"}},
-)
-async def verify_token(
- request: Request,
- auth_service: FromDishka[AuthService],
- logger: FromDishka[logging.Logger],
-) -> TokenValidationResponse:
- """Verify the current access token."""
- current_user = await auth_service.get_current_user(request)
- logger.info(
- "Token verification attempt",
- extra={
- "username": current_user.username,
- "client_ip": get_client_ip(request),
- "endpoint": "/verify-token",
- "user_agent": request.headers.get("user-agent"),
- },
- )
-
- logger.info(
- "Token verification successful",
- extra={
- "username": current_user.username,
- "client_ip": get_client_ip(request),
- "user_agent": request.headers.get("user-agent"),
- },
- )
- csrf_token = request.cookies.get("csrf_token", "")
-
- return TokenValidationResponse(
- valid=True,
- username=current_user.username,
- role="admin" if current_user.is_superuser else "user",
- csrf_token=csrf_token,
- )
-
-
@router.post("/logout", response_model=MessageResponse)
async def logout(
request: Request,
diff --git a/backend/app/api/routes/dlq.py b/backend/app/api/routes/dlq.py
index 2518cf83..46deb936 100644
--- a/backend/app/api/routes/dlq.py
+++ b/backend/app/api/routes/dlq.py
@@ -4,7 +4,7 @@
from dishka.integrations.fastapi import DishkaRoute
from fastapi import APIRouter, Depends, HTTPException, Query
-from app.api.dependencies import current_user
+from app.api.dependencies import admin_user
from app.db.repositories import DLQRepository
from app.dlq import RetryPolicy
from app.dlq.manager import DLQManager
@@ -16,7 +16,6 @@
DLQMessageDetail,
DLQMessageResponse,
DLQMessagesResponse,
- DLQStats,
DLQTopicSummaryResponse,
ManualRetryRequest,
RetryPolicyRequest,
@@ -24,17 +23,10 @@
from app.schemas_pydantic.user import MessageResponse
router = APIRouter(
- prefix="/dlq", tags=["Dead Letter Queue"], route_class=DishkaRoute, dependencies=[Depends(current_user)]
+ prefix="/dlq", tags=["Dead Letter Queue"], route_class=DishkaRoute, dependencies=[Depends(admin_user)]
)
-@router.get("/stats", response_model=DLQStats)
-async def get_dlq_statistics(repository: FromDishka[DLQRepository]) -> DLQStats:
- """Get summary statistics for the dead letter queue."""
- stats = await repository.get_dlq_stats()
- return DLQStats.model_validate(stats, from_attributes=True)
-
-
@router.get("/messages", response_model=DLQMessagesResponse)
async def get_dlq_messages(
repository: FromDishka[DLQRepository],
@@ -80,14 +72,7 @@ async def retry_dlq_messages(
@router.post("/retry-policy", response_model=MessageResponse)
async def set_retry_policy(policy_request: RetryPolicyRequest, dlq_manager: FromDishka[DLQManager]) -> MessageResponse:
"""Configure a retry policy for a specific Kafka topic."""
- policy = RetryPolicy(
- topic=policy_request.topic,
- strategy=policy_request.strategy,
- max_retries=policy_request.max_retries,
- base_delay_seconds=policy_request.base_delay_seconds,
- max_delay_seconds=policy_request.max_delay_seconds,
- retry_multiplier=policy_request.retry_multiplier,
- )
+ policy = RetryPolicy(**policy_request.model_dump())
dlq_manager.set_retry_policy(policy_request.topic, policy)
diff --git a/backend/app/api/routes/events.py b/backend/app/api/routes/events.py
index 10b803fa..0558beb8 100644
--- a/backend/app/api/routes/events.py
+++ b/backend/app/api/routes/events.py
@@ -1,6 +1,6 @@
import logging
from datetime import datetime, timedelta, timezone
-from typing import Annotated, Any
+from typing import Annotated
from dishka import FromDishka
from dishka.integrations.fastapi import DishkaRoute
@@ -15,7 +15,6 @@
from app.schemas_pydantic.common import ErrorResponse
from app.schemas_pydantic.events import (
DeleteEventResponse,
- EventAggregationRequest,
EventFilterRequest,
EventListResponse,
EventStatistics,
@@ -63,13 +62,7 @@ async def get_execution_events(
if result is None:
raise HTTPException(status_code=403, detail="Access denied")
- return EventListResponse(
- events=result.events,
- total=result.total,
- limit=limit,
- skip=skip,
- has_more=result.has_more,
- )
+ return EventListResponse.model_validate(result, from_attributes=True)
@router.get("/user", response_model=EventListResponse)
@@ -94,13 +87,7 @@ async def get_user_events(
sort_order=sort_order,
)
- return EventListResponse(
- events=result.events,
- total=result.total,
- limit=limit,
- skip=skip,
- has_more=result.has_more,
- )
+ return EventListResponse.model_validate(result, from_attributes=True)
@router.post(
@@ -114,16 +101,7 @@ async def query_events(
event_service: FromDishka[EventService],
) -> EventListResponse:
"""Query events with advanced filters."""
- event_filter = EventFilter(
- event_types=filter_request.event_types,
- aggregate_id=filter_request.aggregate_id,
- correlation_id=filter_request.correlation_id,
- user_id=filter_request.user_id,
- service_name=filter_request.service_name,
- start_time=filter_request.start_time,
- end_time=filter_request.end_time,
- search_text=filter_request.search_text,
- )
+ event_filter = EventFilter.model_validate(filter_request, from_attributes=True)
result = await event_service.query_events_advanced(
user_id=current_user.user_id,
@@ -136,13 +114,7 @@ async def query_events(
if result is None:
raise HTTPException(status_code=403, detail="Cannot query other users' events")
- return EventListResponse(
- events=result.events,
- total=result.total,
- limit=result.limit,
- skip=result.skip,
- has_more=result.has_more,
- )
+ return EventListResponse.model_validate(result, from_attributes=True)
@router.get("/correlation/{correlation_id}", response_model=EventListResponse)
@@ -164,13 +136,7 @@ async def get_events_by_correlation(
skip=skip,
)
- return EventListResponse(
- events=result.events,
- total=result.total,
- limit=limit,
- skip=skip,
- has_more=result.has_more,
- )
+ return EventListResponse.model_validate(result, from_attributes=True)
@router.get("/current-request", response_model=EventListResponse)
@@ -194,13 +160,7 @@ async def get_current_request_events(
skip=skip,
)
- return EventListResponse(
- events=result.events,
- total=result.total,
- limit=limit,
- skip=skip,
- has_more=result.has_more,
- )
+ return EventListResponse.model_validate(result, from_attributes=True)
@router.get("/statistics", response_model=EventStatistics)
@@ -276,32 +236,6 @@ async def publish_custom_event(
return PublishEventResponse(event_id=event_id, status="published", timestamp=datetime.now(timezone.utc))
-@router.post("/aggregate", response_model=list[dict[str, Any]])
-async def aggregate_events(
- current_user: Annotated[User, Depends(current_user)],
- aggregation: EventAggregationRequest,
- event_service: FromDishka[EventService],
-) -> list[dict[str, Any]]:
- """Run a custom aggregation pipeline on the event store."""
- result = await event_service.aggregate_events(
- user_id=current_user.user_id,
- user_role=current_user.role,
- pipeline=aggregation.pipeline,
- limit=aggregation.limit,
- )
-
- return result.results
-
-
-@router.get("/types/list", response_model=list[str])
-async def list_event_types(
- current_user: Annotated[User, Depends(current_user)], event_service: FromDishka[EventService]
-) -> list[str]:
- """List all distinct event types in the store."""
- event_types = await event_service.list_event_types(user_id=current_user.user_id, user_role=current_user.role)
- return event_types
-
-
@router.delete(
"/{event_id}",
response_model=DeleteEventResponse,
diff --git a/backend/app/api/routes/execution.py b/backend/app/api/routes/execution.py
index 9314b219..ed5917d4 100644
--- a/backend/app/api/routes/execution.py
+++ b/backend/app/api/routes/execution.py
@@ -1,6 +1,5 @@
-from datetime import datetime, timezone
+from datetime import datetime
from typing import Annotated
-from uuid import uuid4
from dishka import FromDishka
from dishka.integrations.fastapi import DishkaRoute, inject
@@ -10,9 +9,7 @@
from app.core.tracing import EventAttributes, add_span_attributes
from app.core.utils import get_client_ip
from app.domain.enums import EventType, ExecutionStatus, UserRole
-from app.domain.events import BaseEvent, DomainEvent, EventMetadata
-from app.domain.exceptions import DomainError
-from app.domain.idempotency import KeyStrategy
+from app.domain.events import DomainEvent
from app.domain.user import User
from app.schemas_pydantic.common import ErrorResponse
from app.schemas_pydantic.execution import (
@@ -30,9 +27,6 @@
)
from app.services.event_service import EventService
from app.services.execution_service import ExecutionService
-from app.services.idempotency import IdempotencyManager
-from app.services.kafka_event_service import KafkaEventService
-from app.settings import Settings
router = APIRouter(route_class=DishkaRoute, tags=["execution"])
@@ -61,7 +55,6 @@ async def create_execution(
current_user: Annotated[User, Depends(current_user)],
execution: ExecutionRequest,
execution_service: FromDishka[ExecutionService],
- idempotency_manager: FromDishka[IdempotencyManager],
idempotency_key: Annotated[str | None, Header(alias="Idempotency-Key")] = None,
) -> ExecutionResponse:
"""Submit a script for execution in an isolated Kubernetes pod."""
@@ -77,79 +70,16 @@ async def create_execution(
}
)
- # Handle idempotency if key provided
- pseudo_event = None
- if idempotency_key:
- # Create a pseudo-event for idempotency tracking
- pseudo_event = BaseEvent(
- event_id=str(uuid4()),
- event_type=EventType.EXECUTION_REQUESTED,
- timestamp=datetime.now(timezone.utc),
- metadata=EventMetadata(
- user_id=current_user.user_id, correlation_id=str(uuid4()), service_name="api", service_version="1.0.0"
- ),
- )
-
- # Check for duplicate request using custom key
- idempotency_result = await idempotency_manager.check_and_reserve(
- event=pseudo_event,
- key_strategy=KeyStrategy.CUSTOM,
- custom_key=f"http:{current_user.user_id}:{idempotency_key}",
- ttl_seconds=86400, # 24 hours TTL for HTTP idempotency
- )
-
- if idempotency_result.is_duplicate:
- cached_json = await idempotency_manager.get_cached_json(
- event=pseudo_event,
- key_strategy=KeyStrategy.CUSTOM,
- custom_key=f"http:{current_user.user_id}:{idempotency_key}",
- )
- return ExecutionResponse.model_validate_json(cached_json)
-
- try:
- client_ip = get_client_ip(request)
- user_agent = request.headers.get("user-agent")
- exec_result = await execution_service.execute_script(
- script=execution.script,
- lang=execution.lang,
- lang_version=execution.lang_version,
- user_id=current_user.user_id,
- client_ip=client_ip,
- user_agent=user_agent,
- )
-
- # Store result for idempotency if key was provided
- if idempotency_key and pseudo_event:
- response_model = ExecutionResponse.model_validate(exec_result)
- await idempotency_manager.mark_completed_with_json(
- event=pseudo_event,
- cached_json=response_model.model_dump_json(),
- key_strategy=KeyStrategy.CUSTOM,
- custom_key=f"http:{current_user.user_id}:{idempotency_key}",
- )
-
- return ExecutionResponse.model_validate(exec_result)
-
- except DomainError as e:
- # Mark as failed for idempotency
- if idempotency_key and pseudo_event:
- await idempotency_manager.mark_failed(
- event=pseudo_event,
- error=str(e),
- key_strategy=KeyStrategy.CUSTOM,
- custom_key=f"http:{current_user.user_id}:{idempotency_key}",
- )
- raise
- except Exception as e:
- # Mark as failed for idempotency
- if idempotency_key and pseudo_event:
- await idempotency_manager.mark_failed(
- event=pseudo_event,
- error=str(e),
- key_strategy=KeyStrategy.CUSTOM,
- custom_key=f"http:{current_user.user_id}:{idempotency_key}",
- )
- raise HTTPException(status_code=500, detail="Internal server error during script execution") from e
+ exec_result = await execution_service.execute_script_idempotent(
+ script=execution.script,
+ lang=execution.lang,
+ lang_version=execution.lang_version,
+ user_id=current_user.user_id,
+ client_ip=get_client_ip(request),
+ user_agent=request.headers.get("user-agent"),
+ idempotency_key=idempotency_key,
+ )
+ return ExecutionResponse.model_validate(exec_result)
@router.get(
@@ -176,49 +106,16 @@ async def cancel_execution(
execution: Annotated[ExecutionInDB, Depends(get_execution_with_access)],
current_user: Annotated[User, Depends(current_user)],
cancel_request: CancelExecutionRequest,
- event_service: FromDishka[KafkaEventService],
- settings: FromDishka[Settings],
+ execution_service: FromDishka[ExecutionService],
) -> CancelResponse:
"""Cancel a running or queued execution."""
- # Handle terminal states
- terminal_states = [ExecutionStatus.COMPLETED, ExecutionStatus.FAILED, ExecutionStatus.TIMEOUT]
-
- if execution.status in terminal_states:
- raise HTTPException(status_code=400, detail=f"Cannot cancel execution in {str(execution.status)} state")
-
- # Handle idempotency - if already cancelled, return success
- if execution.status == ExecutionStatus.CANCELLED:
- return CancelResponse(
- execution_id=execution.execution_id,
- status="already_cancelled",
- message="Execution was already cancelled",
- event_id="-1", # exact event_id unknown
- )
-
- payload = {
- "execution_id": execution.execution_id,
- "status": str(ExecutionStatus.CANCELLED),
- "reason": cancel_request.reason or "User requested cancellation",
- "previous_status": str(execution.status),
- }
- meta = EventMetadata(
- service_name=settings.SERVICE_NAME,
- service_version=settings.SERVICE_VERSION,
- user_id=current_user.user_id,
- )
- event_id = await event_service.publish_event(
- event_type=EventType.EXECUTION_CANCELLED,
- payload=payload,
- aggregate_id=execution.execution_id,
- metadata=meta,
- )
-
- return CancelResponse(
+ result = await execution_service.cancel_execution(
execution_id=execution.execution_id,
- status="cancellation_requested",
- message="Cancellation request submitted",
- event_id=event_id,
+ current_status=execution.status,
+ user_id=current_user.user_id,
+ reason=cancel_request.reason,
)
+ return CancelResponse.model_validate(result, from_attributes=True)
@router.post(
diff --git a/backend/app/api/routes/grafana_alerts.py b/backend/app/api/routes/grafana_alerts.py
deleted file mode 100644
index 21f30c38..00000000
--- a/backend/app/api/routes/grafana_alerts.py
+++ /dev/null
@@ -1,39 +0,0 @@
-from dishka import FromDishka
-from dishka.integrations.fastapi import DishkaRoute
-from fastapi import APIRouter
-
-from app.core.correlation import CorrelationContext
-from app.schemas_pydantic.grafana import AlertResponse, GrafanaWebhook
-from app.services.grafana_alert_processor import GrafanaAlertProcessor
-
-router = APIRouter(prefix="/alerts", tags=["alerts"], route_class=DishkaRoute)
-
-
-@router.post("/grafana", response_model=AlertResponse)
-async def receive_grafana_alerts(
- webhook_payload: GrafanaWebhook,
- processor: FromDishka[GrafanaAlertProcessor],
-) -> AlertResponse:
- """Receive and process a Grafana alerting webhook payload."""
- correlation_id = CorrelationContext.get_correlation_id()
-
- processed_count, errors = await processor.process_webhook(webhook_payload, correlation_id)
-
- alerts_count = len(webhook_payload.alerts or [])
-
- return AlertResponse(
- message="Webhook received and processed",
- alerts_received=alerts_count,
- alerts_processed=processed_count,
- errors=errors,
- )
-
-
-@router.get("/grafana/test")
-async def test_grafana_alert_endpoint() -> dict[str, str]:
- """Verify the Grafana webhook endpoint is reachable."""
- return {
- "status": "ok",
- "message": "Grafana webhook endpoint is ready",
- "webhook_url": "/api/v1/alerts/grafana",
- }
diff --git a/backend/app/api/routes/health.py b/backend/app/api/routes/health.py
index aecbd1e2..2768f398 100644
--- a/backend/app/api/routes/health.py
+++ b/backend/app/api/routes/health.py
@@ -18,13 +18,6 @@ class LivenessResponse(BaseModel):
timestamp: datetime = Field(description="Timestamp of health check")
-class ReadinessResponse(BaseModel):
- """Response model for readiness probe."""
-
- status: str = Field(description="Readiness status")
- uptime_seconds: int = Field(description="Server uptime in seconds")
-
-
@router.get("/live", response_model=LivenessResponse)
async def liveness() -> LivenessResponse:
"""Basic liveness probe. Does not touch external deps."""
@@ -33,13 +26,3 @@ async def liveness() -> LivenessResponse:
uptime_seconds=int(time.time() - _START_TIME),
timestamp=datetime.now(timezone.utc),
)
-
-
-@router.get("/ready", response_model=ReadinessResponse)
-async def readiness() -> ReadinessResponse:
- """Simple readiness probe. Extend with dependency checks if needed."""
- # Keep it simple and fast. Add checks (DB ping, Kafka ping) when desired.
- return ReadinessResponse(
- status="ok",
- uptime_seconds=int(time.time() - _START_TIME),
- )
diff --git a/backend/app/api/routes/notifications.py b/backend/app/api/routes/notifications.py
index 1549a35e..507606d5 100644
--- a/backend/app/api/routes/notifications.py
+++ b/backend/app/api/routes/notifications.py
@@ -61,8 +61,7 @@ async def mark_notification_read(
) -> Response:
"""Mark a single notification as read."""
current_user = await auth_service.get_current_user(request)
- _ = await notification_service.mark_as_read(notification_id=notification_id, user_id=current_user.user_id)
-
+ await notification_service.mark_as_read(notification_id=notification_id, user_id=current_user.user_id)
return Response(status_code=204)
@@ -131,5 +130,5 @@ async def delete_notification(
) -> DeleteNotificationResponse:
"""Delete a notification."""
current_user = await auth_service.get_current_user(request)
- _ = await notification_service.delete_notification(user_id=current_user.user_id, notification_id=notification_id)
+ await notification_service.delete_notification(user_id=current_user.user_id, notification_id=notification_id)
return DeleteNotificationResponse(message="Notification deleted")
diff --git a/backend/app/api/routes/replay.py b/backend/app/api/routes/replay.py
index 9e519c55..258cb860 100644
--- a/backend/app/api/routes/replay.py
+++ b/backend/app/api/routes/replay.py
@@ -25,8 +25,8 @@ async def create_replay_session(
service: FromDishka[EventReplayService],
) -> ReplayResponse:
"""Create a new event replay session from a configuration."""
- result = await service.create_session_from_config(ReplayConfig(**replay_request.model_dump()))
- return ReplayResponse(**result.model_dump())
+ result = await service.create_session_from_config(ReplayConfig.model_validate(replay_request))
+ return ReplayResponse.model_validate(result)
@router.post("/sessions/{session_id}/start", response_model=ReplayResponse)
@@ -36,7 +36,7 @@ async def start_replay_session(
) -> ReplayResponse:
"""Start a previously created replay session."""
result = await service.start_session(session_id)
- return ReplayResponse(**result.model_dump())
+ return ReplayResponse.model_validate(result)
@router.post("/sessions/{session_id}/pause", response_model=ReplayResponse)
@@ -46,21 +46,21 @@ async def pause_replay_session(
) -> ReplayResponse:
"""Pause a running replay session."""
result = await service.pause_session(session_id)
- return ReplayResponse(**result.model_dump())
+ return ReplayResponse.model_validate(result)
@router.post("/sessions/{session_id}/resume", response_model=ReplayResponse)
async def resume_replay_session(session_id: str, service: FromDishka[EventReplayService]) -> ReplayResponse:
"""Resume a paused replay session."""
result = await service.resume_session(session_id)
- return ReplayResponse(**result.model_dump())
+ return ReplayResponse.model_validate(result)
@router.post("/sessions/{session_id}/cancel", response_model=ReplayResponse)
async def cancel_replay_session(session_id: str, service: FromDishka[EventReplayService]) -> ReplayResponse:
"""Cancel and stop a replay session."""
result = await service.cancel_session(session_id)
- return ReplayResponse(**result.model_dump())
+ return ReplayResponse.model_validate(result)
@router.get("/sessions", response_model=list[SessionSummary])
@@ -70,10 +70,7 @@ async def list_replay_sessions(
limit: Annotated[int, Query(ge=1, le=1000)] = 100,
) -> list[SessionSummary]:
"""List replay sessions with optional status filtering."""
- return [
- SessionSummary(**{**s.model_dump(), **s.config.model_dump()})
- for s in service.list_sessions(status=status, limit=limit)
- ]
+ return [SessionSummary.model_validate(s) for s in service.list_sessions(status=status, limit=limit)]
@router.get("/sessions/{session_id}", response_model=ReplaySession)
@@ -89,4 +86,4 @@ async def cleanup_old_sessions(
) -> CleanupResponse:
"""Remove replay sessions older than the specified threshold."""
result = await service.cleanup_old_sessions(older_than_hours)
- return CleanupResponse(**result.model_dump())
+ return CleanupResponse.model_validate(result)
diff --git a/backend/app/api/routes/saga.py b/backend/app/api/routes/saga.py
index d4d00808..b44072e5 100644
--- a/backend/app/api/routes/saga.py
+++ b/backend/app/api/routes/saga.py
@@ -2,17 +2,17 @@
from dishka import FromDishka
from dishka.integrations.fastapi import DishkaRoute
-from fastapi import APIRouter, Query, Request
+from fastapi import APIRouter, Depends, Query
+from app.api.dependencies import current_user
from app.domain.enums import SagaState
+from app.domain.user import User
from app.schemas_pydantic.common import ErrorResponse
from app.schemas_pydantic.saga import (
SagaCancellationResponse,
SagaListResponse,
SagaStatusResponse,
)
-from app.schemas_pydantic.user import User
-from app.services.auth_service import AuthService
from app.services.saga.saga_service import SagaService
router = APIRouter(
@@ -32,26 +32,10 @@
)
async def get_saga_status(
saga_id: str,
- request: Request,
+ user: Annotated[User, Depends(current_user)],
saga_service: FromDishka[SagaService],
- auth_service: FromDishka[AuthService],
) -> SagaStatusResponse:
- """Get saga status by ID.
-
- Args:
- saga_id: The saga identifier
- request: FastAPI request object
- saga_service: Saga service from DI
- auth_service: Auth service from DI
-
- Returns:
- Saga status response
-
- Raises:
- HTTPException: 404 if saga not found, 403 if access denied
- """
- current_user = await auth_service.get_current_user(request)
- user = User.model_validate(current_user)
+ """Get saga status by ID."""
saga = await saga_service.get_saga_with_access_check(saga_id, user)
return SagaStatusResponse.model_validate(saga)
@@ -63,32 +47,13 @@ async def get_saga_status(
)
async def get_execution_sagas(
execution_id: str,
- request: Request,
+ user: Annotated[User, Depends(current_user)],
saga_service: FromDishka[SagaService],
- auth_service: FromDishka[AuthService],
state: Annotated[SagaState | None, Query(description="Filter by saga state")] = None,
limit: Annotated[int, Query(ge=1, le=1000)] = 100,
skip: Annotated[int, Query(ge=0)] = 0,
) -> SagaListResponse:
- """Get all sagas for an execution.
-
- Args:
- execution_id: The execution identifier
- request: FastAPI request object
- saga_service: Saga service from DI
- auth_service: Auth service from DI
- state: Optional state filter
- limit: Maximum number of results
- skip: Number of results to skip
-
- Returns:
- Paginated list of sagas for the execution
-
- Raises:
- HTTPException: 403 if access denied
- """
- current_user = await auth_service.get_current_user(request)
- user = User.model_validate(current_user)
+ """Get all sagas for an execution."""
result = await saga_service.get_execution_sagas(execution_id, user, state, limit=limit, skip=skip)
saga_responses = [SagaStatusResponse.model_validate(s) for s in result.sagas]
return SagaListResponse(
@@ -102,28 +67,13 @@ async def get_execution_sagas(
@router.get("/", response_model=SagaListResponse)
async def list_sagas(
- request: Request,
+ user: Annotated[User, Depends(current_user)],
saga_service: FromDishka[SagaService],
- auth_service: FromDishka[AuthService],
state: Annotated[SagaState | None, Query(description="Filter by saga state")] = None,
limit: Annotated[int, Query(ge=1, le=1000)] = 100,
skip: Annotated[int, Query(ge=0)] = 0,
) -> SagaListResponse:
- """List sagas accessible by the current user.
-
- Args:
- request: FastAPI request object
- saga_service: Saga service from DI
- auth_service: Auth service from DI
- state: Optional state filter
- limit: Maximum number of results
- skip: Number of results to skip
-
- Returns:
- Paginated list of sagas
- """
- current_user = await auth_service.get_current_user(request)
- user = User.model_validate(current_user)
+ """List sagas accessible by the current user."""
result = await saga_service.list_user_sagas(user, state, limit, skip)
saga_responses = [SagaStatusResponse.model_validate(s) for s in result.sagas]
return SagaListResponse(
@@ -146,26 +96,10 @@ async def list_sagas(
)
async def cancel_saga(
saga_id: str,
- request: Request,
+ user: Annotated[User, Depends(current_user)],
saga_service: FromDishka[SagaService],
- auth_service: FromDishka[AuthService],
) -> SagaCancellationResponse:
- """Cancel a running saga.
-
- Args:
- saga_id: The saga identifier
- request: FastAPI request object
- saga_service: Saga service from DI
- auth_service: Auth service from DI
-
- Returns:
- Cancellation response with success status
-
- Raises:
- HTTPException: 404 if not found, 403 if denied, 400 if invalid state
- """
- current_user = await auth_service.get_current_user(request)
- user = User.model_validate(current_user)
+ """Cancel a running saga."""
success = await saga_service.cancel_saga(saga_id, user)
return SagaCancellationResponse(
diff --git a/backend/app/api/routes/saved_scripts.py b/backend/app/api/routes/saved_scripts.py
index 1d5bbb77..9f89c08b 100644
--- a/backend/app/api/routes/saved_scripts.py
+++ b/backend/app/api/routes/saved_scripts.py
@@ -1,15 +1,18 @@
+from typing import Annotated
+
from dishka import FromDishka
from dishka.integrations.fastapi import DishkaRoute
-from fastapi import APIRouter, Request
+from fastapi import APIRouter, Depends
+from app.api.dependencies import current_user
from app.domain.saved_script import DomainSavedScriptCreate, DomainSavedScriptUpdate
+from app.domain.user import User
from app.schemas_pydantic.common import ErrorResponse
from app.schemas_pydantic.saved_script import (
SavedScriptCreateRequest,
SavedScriptResponse,
SavedScriptUpdate,
)
-from app.services.auth_service import AuthService
from app.services.saved_script_service import SavedScriptService
router = APIRouter(route_class=DishkaRoute, tags=["scripts"])
@@ -17,27 +20,23 @@
@router.post("/scripts", response_model=SavedScriptResponse)
async def create_saved_script(
- request: Request,
+ user: Annotated[User, Depends(current_user)],
saved_script: SavedScriptCreateRequest,
saved_script_service: FromDishka[SavedScriptService],
- auth_service: FromDishka[AuthService],
) -> SavedScriptResponse:
"""Save a new script to the user's collection."""
- current_user = await auth_service.get_current_user(request)
- create = DomainSavedScriptCreate(**saved_script.model_dump())
- domain = await saved_script_service.create_saved_script(create, current_user.user_id)
+ create = DomainSavedScriptCreate.model_validate(saved_script)
+ domain = await saved_script_service.create_saved_script(create, user.user_id)
return SavedScriptResponse.model_validate(domain)
@router.get("/scripts", response_model=list[SavedScriptResponse])
async def list_saved_scripts(
- request: Request,
+ user: Annotated[User, Depends(current_user)],
saved_script_service: FromDishka[SavedScriptService],
- auth_service: FromDishka[AuthService],
) -> list[SavedScriptResponse]:
"""List all saved scripts for the authenticated user."""
- current_user = await auth_service.get_current_user(request)
- items = await saved_script_service.list_saved_scripts(current_user.user_id)
+ items = await saved_script_service.list_saved_scripts(user.user_id)
return [SavedScriptResponse.model_validate(item) for item in items]
@@ -47,15 +46,12 @@ async def list_saved_scripts(
responses={404: {"model": ErrorResponse, "description": "Script not found"}},
)
async def get_saved_script(
- request: Request,
script_id: str,
+ user: Annotated[User, Depends(current_user)],
saved_script_service: FromDishka[SavedScriptService],
- auth_service: FromDishka[AuthService],
) -> SavedScriptResponse:
"""Get a saved script by ID."""
- current_user = await auth_service.get_current_user(request)
-
- domain = await saved_script_service.get_saved_script(script_id, current_user.user_id)
+ domain = await saved_script_service.get_saved_script(script_id, user.user_id)
return SavedScriptResponse.model_validate(domain)
@@ -65,17 +61,14 @@ async def get_saved_script(
responses={404: {"model": ErrorResponse, "description": "Script not found"}},
)
async def update_saved_script(
- request: Request,
script_id: str,
script_update: SavedScriptUpdate,
+ user: Annotated[User, Depends(current_user)],
saved_script_service: FromDishka[SavedScriptService],
- auth_service: FromDishka[AuthService],
) -> SavedScriptResponse:
"""Update an existing saved script."""
- current_user = await auth_service.get_current_user(request)
-
- update_data = DomainSavedScriptUpdate(**script_update.model_dump())
- domain = await saved_script_service.update_saved_script(script_id, current_user.user_id, update_data)
+ update_data = DomainSavedScriptUpdate.model_validate(script_update)
+ domain = await saved_script_service.update_saved_script(script_id, user.user_id, update_data)
return SavedScriptResponse.model_validate(domain)
@@ -85,13 +78,9 @@ async def update_saved_script(
responses={404: {"model": ErrorResponse, "description": "Script not found"}},
)
async def delete_saved_script(
- request: Request,
script_id: str,
+ user: Annotated[User, Depends(current_user)],
saved_script_service: FromDishka[SavedScriptService],
- auth_service: FromDishka[AuthService],
) -> None:
"""Delete a saved script."""
- current_user = await auth_service.get_current_user(request)
-
- await saved_script_service.delete_saved_script(script_id, current_user.user_id)
- return None
+ await saved_script_service.delete_saved_script(script_id, user.user_id)
diff --git a/backend/app/api/routes/sse.py b/backend/app/api/routes/sse.py
index cc05e10c..cfbfc7a9 100644
--- a/backend/app/api/routes/sse.py
+++ b/backend/app/api/routes/sse.py
@@ -1,38 +1,46 @@
+from typing import Annotated
+
from dishka import FromDishka
from dishka.integrations.fastapi import DishkaRoute
-from fastapi import APIRouter, Request
+from fastapi import APIRouter, Depends
from sse_starlette.sse import EventSourceResponse
+from app.api.dependencies import current_user
+from app.domain.user import User
from app.schemas_pydantic.notification import NotificationResponse
from app.schemas_pydantic.sse import SSEExecutionEventData
-from app.services.auth_service import AuthService
from app.services.sse import SSEService
router = APIRouter(prefix="/events", tags=["sse"], route_class=DishkaRoute)
-@router.get("/notifications/stream", responses={200: {"model": NotificationResponse}})
+@router.get(
+ "/notifications/stream",
+ response_class=EventSourceResponse,
+ responses={200: {"model": NotificationResponse}},
+)
async def notification_stream(
- request: Request,
+ user: Annotated[User, Depends(current_user)],
sse_service: FromDishka[SSEService],
- auth_service: FromDishka[AuthService],
) -> EventSourceResponse:
"""Stream notifications for authenticated user."""
- current_user = await auth_service.get_current_user(request)
-
return EventSourceResponse(
- sse_service.create_notification_stream(user_id=current_user.user_id),
+ sse_service.create_notification_stream(user_id=user.user_id),
ping=30,
)
-@router.get("/executions/{execution_id}", responses={200: {"model": SSEExecutionEventData}})
+@router.get(
+ "/executions/{execution_id}",
+ response_class=EventSourceResponse,
+ responses={200: {"model": SSEExecutionEventData}},
+)
async def execution_events(
- execution_id: str, request: Request, sse_service: FromDishka[SSEService], auth_service: FromDishka[AuthService]
+ execution_id: str,
+ user: Annotated[User, Depends(current_user)],
+ sse_service: FromDishka[SSEService],
) -> EventSourceResponse:
"""Stream events for specific execution."""
- current_user = await auth_service.get_current_user(request)
-
return EventSourceResponse(
- sse_service.create_execution_stream(execution_id=execution_id, user_id=current_user.user_id)
+ sse_service.create_execution_stream(execution_id=execution_id, user_id=user.user_id)
)
diff --git a/backend/app/api/routes/user_settings.py b/backend/app/api/routes/user_settings.py
index a00d7279..97487c25 100644
--- a/backend/app/api/routes/user_settings.py
+++ b/backend/app/api/routes/user_settings.py
@@ -43,17 +43,7 @@ async def update_user_settings(
settings_service: FromDishka[UserSettingsService],
) -> UserSettings:
"""Update the authenticated user's settings."""
- domain_updates = DomainUserSettingsUpdate(
- theme=updates.theme,
- timezone=updates.timezone,
- date_format=updates.date_format,
- time_format=updates.time_format,
- notifications=(
- DomainNotificationSettings(**updates.notifications.model_dump()) if updates.notifications else None
- ),
- editor=DomainEditorSettings(**updates.editor.model_dump()) if updates.editor else None,
- custom_settings=updates.custom_settings,
- )
+ domain_updates = DomainUserSettingsUpdate.model_validate(updates)
domain = await settings_service.update_user_settings(current_user.user_id, domain_updates)
return UserSettings.model_validate(domain)
@@ -78,7 +68,7 @@ async def update_notification_settings(
"""Update notification preferences."""
domain = await settings_service.update_notification_settings(
current_user.user_id,
- DomainNotificationSettings(**notifications.model_dump()),
+ DomainNotificationSettings.model_validate(notifications),
)
return UserSettings.model_validate(domain)
@@ -92,7 +82,7 @@ async def update_editor_settings(
"""Update code editor preferences."""
domain = await settings_service.update_editor_settings(
current_user.user_id,
- DomainEditorSettings(**editor.model_dump()),
+ DomainEditorSettings.model_validate(editor),
)
return UserSettings.model_validate(domain)
diff --git a/backend/app/core/metrics/execution.py b/backend/app/core/metrics/execution.py
index 4d7f59a9..72132a9c 100644
--- a/backend/app/core/metrics/execution.py
+++ b/backend/app/core/metrics/execution.py
@@ -92,7 +92,7 @@ def record_execution_assigned(self) -> None:
def record_execution_queued(self) -> None:
self.executions_queued.add(1)
- def record_execution_scheduled(self, status: str) -> None:
+ def record_execution_scheduled(self) -> None:
self.executions_assigned.add(1)
def update_cpu_available(self, cores: float) -> None:
diff --git a/backend/app/core/middlewares/cache.py b/backend/app/core/middlewares/cache.py
index 925149f1..73778232 100644
--- a/backend/app/core/middlewares/cache.py
+++ b/backend/app/core/middlewares/cache.py
@@ -8,7 +8,6 @@ def __init__(self, app: ASGIApp):
self.cache_policies: dict[str, str] = {
"/api/v1/k8s-limits": "public, max-age=300", # 5 minutes
"/api/v1/example-scripts": "public, max-age=600", # 10 minutes
- "/api/v1/auth/verify-token": "private, no-cache", # 30 seconds
"/api/v1/notifications": "private, no-cache", # Always revalidate
"/api/v1/notifications/unread-count": "private, no-cache", # Always revalidate
}
diff --git a/backend/app/core/providers.py b/backend/app/core/providers.py
index 4f303a31..744040bc 100644
--- a/backend/app/core/providers.py
+++ b/backend/app/core/providers.py
@@ -56,7 +56,6 @@
from app.services.event_replay.replay_service import EventReplayService
from app.services.event_service import EventService
from app.services.execution_service import ExecutionService
-from app.services.grafana_alert_processor import GrafanaAlertProcessor
from app.services.idempotency import IdempotencyConfig, IdempotencyManager
from app.services.idempotency.redis_repository import RedisIdempotencyRepository
from app.services.k8s_worker import KubernetesWorker
@@ -623,14 +622,6 @@ async def get_notification_scheduler(
apscheduler.shutdown(wait=False)
logger.info("NotificationScheduler stopped")
- @provide
- def get_grafana_alert_processor(
- self,
- notification_service: NotificationService,
- logger: logging.Logger,
- ) -> GrafanaAlertProcessor:
- return GrafanaAlertProcessor(notification_service, logger)
-
def _create_default_saga_config() -> SagaConfig:
"""Factory for default SagaConfig used by orchestrators.
@@ -673,18 +664,18 @@ def get_execution_service(
self,
execution_repository: ExecutionRepository,
kafka_producer: UnifiedProducer,
- event_repository: EventRepository,
settings: Settings,
logger: logging.Logger,
execution_metrics: ExecutionMetrics,
+ idempotency_manager: IdempotencyManager,
) -> ExecutionService:
return ExecutionService(
execution_repo=execution_repository,
producer=kafka_producer,
- event_repository=event_repository,
settings=settings,
logger=logger,
execution_metrics=execution_metrics,
+ idempotency_manager=idempotency_manager,
)
@provide
diff --git a/backend/app/db/repositories/admin/admin_events_repository.py b/backend/app/db/repositories/admin/admin_events_repository.py
index 76f1e320..e22a20ea 100644
--- a/backend/app/db/repositories/admin/admin_events_repository.py
+++ b/backend/app/db/repositories/admin/admin_events_repository.py
@@ -12,7 +12,7 @@
ReplaySessionDocument,
)
from app.domain.admin import ExecutionResultSummary, ReplaySessionData, ReplaySessionStatusDetail, ReplaySessionUpdate
-from app.domain.enums import EventType, ReplayStatus
+from app.domain.enums import EventType, ExecutionStatus, ReplayStatus
from app.domain.events import (
DomainEvent,
DomainEventAdapter,
@@ -176,7 +176,7 @@ async def get_event_stats(self, hours: int = 24) -> EventStatistics:
Pipeline()
.match({
ExecutionDocument.created_at: {"$gte": start_time},
- ExecutionDocument.status: "completed",
+ ExecutionDocument.status: ExecutionStatus.COMPLETED,
ExecutionDocument.resource_usage.execution_time_wall_seconds: {"$exists": True}, # type: ignore[union-attr]
})
.group(by=None, query={"avg_duration": S.avg(exec_time_field)})
diff --git a/backend/app/db/repositories/dlq_repository.py b/backend/app/db/repositories/dlq_repository.py
index 1a94a617..0688c654 100644
--- a/backend/app/db/repositories/dlq_repository.py
+++ b/backend/app/db/repositories/dlq_repository.py
@@ -8,15 +8,11 @@
from app.db.docs import DLQMessageDocument
from app.dlq import (
- AgeStatistics,
DLQMessage,
DLQMessageListResult,
DLQMessageStatus,
DLQMessageUpdate,
- DLQStatistics,
DLQTopicSummary,
- EventTypeStatistic,
- TopicStatistic,
)
from app.domain.enums import EventType
@@ -25,73 +21,6 @@ class DLQRepository:
def __init__(self, logger: logging.Logger):
self.logger = logger
- async def get_dlq_stats(self) -> DLQStatistics:
- # Counts by status
- status_pipeline = Pipeline().group(by=S.field(DLQMessageDocument.status), query={"count": S.sum(1)})
- status_results = await DLQMessageDocument.aggregate(status_pipeline.export()).to_list()
- by_status = {doc["_id"]: doc["count"] for doc in status_results if doc["_id"]}
-
- # Counts by topic (top 10) - project renames _id->topic and rounds avg_retry_count
- topic_pipeline = (
- Pipeline()
- .group(
- by=S.field(DLQMessageDocument.original_topic),
- query={"count": S.sum(1), "avg_retry_count": S.avg(S.field(DLQMessageDocument.retry_count))},
- )
- .sort(by="count", descending=True)
- .limit(10)
- .project(_id=0, topic="$_id", count=1, avg_retry_count={"$round": ["$avg_retry_count", 2]})
- )
- topic_results = await DLQMessageDocument.aggregate(topic_pipeline.export()).to_list()
- by_topic = [TopicStatistic.model_validate(doc) for doc in topic_results]
-
- # Counts by event type (top 10) - project renames _id->event_type
- event_type_pipeline = (
- Pipeline()
- .group(by=S.field(DLQMessageDocument.event.event_type), query={"count": S.sum(1)})
- .sort(by="count", descending=True)
- .limit(10)
- .project(_id=0, event_type="$_id", count=1)
- )
- event_type_results = await DLQMessageDocument.aggregate(event_type_pipeline.export()).to_list()
- by_event_type = [EventTypeStatistic.model_validate(doc) for doc in event_type_results if doc["event_type"]]
-
- # Age statistics - use $toLong to convert Date to milliseconds for $avg
- time_pipeline = Pipeline().group(
- by=None,
- query={
- "oldest": S.min(S.field(DLQMessageDocument.failed_at)),
- "newest": S.max(S.field(DLQMessageDocument.failed_at)),
- "avg_failed_at_ms": {"$avg": {"$toLong": S.field(DLQMessageDocument.failed_at)}},
- },
- )
- time_results = await DLQMessageDocument.aggregate(time_pipeline.export()).to_list()
- now = datetime.now(timezone.utc)
- if time_results and time_results[0].get("oldest"):
- r = time_results[0]
- oldest, newest = r["oldest"], r["newest"]
- # MongoDB returns naive datetimes (implicitly UTC), make them aware
- if oldest.tzinfo is None:
- oldest = oldest.replace(tzinfo=timezone.utc)
- if newest.tzinfo is None:
- newest = newest.replace(tzinfo=timezone.utc)
- # Convert average timestamp (ms) back to datetime for age calculation
- avg_failed_at_ms = r.get("avg_failed_at_ms")
- if avg_failed_at_ms:
- avg_failed_at = datetime.fromtimestamp(avg_failed_at_ms / 1000, tz=timezone.utc)
- avg_age_seconds = (now - avg_failed_at).total_seconds()
- else:
- avg_age_seconds = 0.0
- age_stats = AgeStatistics(
- min_age_seconds=(now - newest).total_seconds(),
- max_age_seconds=(now - oldest).total_seconds(),
- avg_age_seconds=avg_age_seconds,
- )
- else:
- age_stats = AgeStatistics()
-
- return DLQStatistics(by_status=by_status, by_topic=by_topic, by_event_type=by_event_type, age_stats=age_stats)
-
async def get_messages(
self,
status: DLQMessageStatus | None = None,
diff --git a/backend/app/db/repositories/event_repository.py b/backend/app/db/repositories/event_repository.py
index 9a304a0c..3a82a7fd 100644
--- a/backend/app/db/repositories/event_repository.py
+++ b/backend/app/db/repositories/event_repository.py
@@ -16,7 +16,6 @@
ArchivedEvent,
DomainEvent,
DomainEventAdapter,
- EventAggregationResult,
EventListResult,
EventReplayInfo,
EventStatistics,
@@ -274,26 +273,6 @@ async def query_events(
events=events, total=total_count, skip=skip, limit=limit, has_more=(skip + limit) < total_count
)
- async def aggregate_events(self, pipeline: list[dict[str, Any]], limit: int = 100) -> EventAggregationResult:
- """Run aggregation pipeline on events."""
- pipeline_with_limit = [*pipeline, {"$limit": limit}]
- results = await EventDocument.aggregate(pipeline_with_limit).to_list()
- return EventAggregationResult(results=results, pipeline=pipeline_with_limit)
-
- async def list_event_types(self, match: dict[str, object] | None = None) -> list[str]:
- """List distinct event types, optionally filtered."""
- pipeline: list[dict[str, object]] = []
- if match:
- pipeline.append({"$match": match})
- pipeline.extend(
- [
- {"$group": {"_id": S.field(EventDocument.event_type)}},
- {"$sort": {"_id": 1}},
- ]
- )
- results: list[dict[str, str]] = await EventDocument.aggregate(pipeline).to_list()
- return [doc["_id"] for doc in results if doc.get("_id")]
-
async def delete_event_with_archival(
self, event_id: str, deleted_by: str, deletion_reason: str = "Admin deletion via API"
) -> ArchivedEvent | None:
diff --git a/backend/app/db/repositories/user_repository.py b/backend/app/db/repositories/user_repository.py
index 7e72377d..4ac576fe 100644
--- a/backend/app/db/repositories/user_repository.py
+++ b/backend/app/db/repositories/user_repository.py
@@ -3,9 +3,11 @@
from beanie.odm.operators.find import BaseFindOperator
from beanie.operators import Eq, Or, RegEx
+from pymongo.errors import DuplicateKeyError
from app.db.docs import UserDocument
from app.domain.enums import UserRole
+from app.domain.exceptions import ConflictError
from app.domain.user import DomainUserCreate, DomainUserUpdate, User, UserListResult
@@ -16,7 +18,10 @@ async def get_user(self, username: str) -> User | None:
async def create_user(self, create_data: DomainUserCreate) -> User:
doc = UserDocument(**create_data.model_dump())
- await doc.insert()
+ try:
+ await doc.insert()
+ except DuplicateKeyError as e:
+ raise ConflictError("User already exists") from e
return User.model_validate(doc, from_attributes=True)
async def get_user_by_id(self, user_id: str) -> User | None:
diff --git a/backend/app/dlq/__init__.py b/backend/app/dlq/__init__.py
index f047e9c4..73f37738 100644
--- a/backend/app/dlq/__init__.py
+++ b/backend/app/dlq/__init__.py
@@ -5,7 +5,6 @@
"""
from .models import (
- AgeStatistics,
DLQBatchRetryResult,
DLQMessage,
DLQMessageFilter,
@@ -13,12 +12,9 @@
DLQMessageStatus,
DLQMessageUpdate,
DLQRetryResult,
- DLQStatistics,
DLQTopicSummary,
- EventTypeStatistic,
RetryPolicy,
RetryStrategy,
- TopicStatistic,
)
__all__ = [
@@ -28,10 +24,6 @@
"DLQMessageUpdate",
"DLQMessageFilter",
"RetryPolicy",
- "TopicStatistic",
- "EventTypeStatistic",
- "AgeStatistics",
- "DLQStatistics",
"DLQRetryResult",
"DLQBatchRetryResult",
"DLQMessageListResult",
diff --git a/backend/app/dlq/models.py b/backend/app/dlq/models.py
index 85e91a09..1af6d92e 100644
--- a/backend/app/dlq/models.py
+++ b/backend/app/dlq/models.py
@@ -115,48 +115,6 @@ def get_next_retry_time(self, message: DLQMessage) -> datetime:
return datetime.now(timezone.utc) + timedelta(seconds=delay)
-# Statistics models
-class TopicStatistic(BaseModel):
- """Statistics for a single topic."""
-
- model_config = ConfigDict(from_attributes=True)
-
- topic: str
- count: int
- avg_retry_count: float
-
-
-class EventTypeStatistic(BaseModel):
- """Statistics for a single event type."""
-
- model_config = ConfigDict(from_attributes=True)
-
- event_type: str
- count: int
-
-
-class AgeStatistics(BaseModel):
- """Age statistics for DLQ messages."""
-
- model_config = ConfigDict(from_attributes=True)
-
- min_age_seconds: float = 0.0
- max_age_seconds: float = 0.0
- avg_age_seconds: float = 0.0
-
-
-class DLQStatistics(BaseModel):
- """Comprehensive DLQ statistics."""
-
- model_config = ConfigDict(from_attributes=True)
-
- by_status: dict[DLQMessageStatus, int]
- by_topic: list[TopicStatistic]
- by_event_type: list[EventTypeStatistic]
- age_stats: AgeStatistics
- timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
-
-
@dataclass
class DLQRetryResult:
"""Result of a single retry operation."""
diff --git a/backend/app/domain/enums/__init__.py b/backend/app/domain/enums/__init__.py
index cfdbe8b9..53254742 100644
--- a/backend/app/domain/enums/__init__.py
+++ b/backend/app/domain/enums/__init__.py
@@ -1,8 +1,7 @@
from app.domain.enums.auth import LoginMethod, SettingsType
from app.domain.enums.common import Environment, ErrorType, SortOrder, Theme
from app.domain.enums.events import EventType
-from app.domain.enums.execution import ExecutionStatus, QueuePriority
-from app.domain.enums.health import AlertSeverity, AlertStatus, ComponentStatus
+from app.domain.enums.execution import CancelStatus, ExecutionStatus, QueuePriority
from app.domain.enums.kafka import GroupId, KafkaTopic
from app.domain.enums.notification import (
NotificationChannel,
@@ -27,12 +26,9 @@
# Events
"EventType",
# Execution
+ "CancelStatus",
"ExecutionStatus",
"QueuePriority",
- # Health
- "AlertSeverity",
- "AlertStatus",
- "ComponentStatus",
# Kafka
"GroupId",
"KafkaTopic",
diff --git a/backend/app/domain/enums/execution.py b/backend/app/domain/enums/execution.py
index 22b8764c..333f4c61 100644
--- a/backend/app/domain/enums/execution.py
+++ b/backend/app/domain/enums/execution.py
@@ -22,3 +22,10 @@ class ExecutionStatus(StringEnum):
TIMEOUT = "timeout"
CANCELLED = "cancelled"
ERROR = "error"
+
+
+class CancelStatus(StringEnum):
+ """Outcome of a cancel request."""
+
+ ALREADY_CANCELLED = "already_cancelled"
+ CANCELLATION_REQUESTED = "cancellation_requested"
diff --git a/backend/app/domain/enums/health.py b/backend/app/domain/enums/health.py
deleted file mode 100644
index 00985562..00000000
--- a/backend/app/domain/enums/health.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from app.core.utils import StringEnum
-
-
-class AlertSeverity(StringEnum):
- """Alert severity levels."""
-
- CRITICAL = "critical"
- WARNING = "warning"
- INFO = "info"
-
-
-class AlertStatus(StringEnum):
- """Alert status."""
-
- FIRING = "firing"
- RESOLVED = "resolved"
-
-
-class ComponentStatus(StringEnum):
- """Health check component status."""
-
- HEALTHY = "healthy"
- DEGRADED = "degraded"
- UNHEALTHY = "unhealthy"
- UNKNOWN = "unknown"
diff --git a/backend/app/domain/events/__init__.py b/backend/app/domain/events/__init__.py
index b6dc3b56..da7ee33a 100644
--- a/backend/app/domain/events/__init__.py
+++ b/backend/app/domain/events/__init__.py
@@ -1,5 +1,4 @@
from app.domain.events.event_models import (
- EventAggregationResult,
EventBrowseResult,
EventDetail,
EventExportRow,
@@ -101,7 +100,6 @@
__all__ = [
# Query/filter/result types
- "EventAggregationResult",
"EventBrowseResult",
"EventDetail",
"EventExportRow",
diff --git a/backend/app/domain/events/event_models.py b/backend/app/domain/events/event_models.py
index 3bdf58e7..aa31e7bd 100644
--- a/backend/app/domain/events/event_models.py
+++ b/backend/app/domain/events/event_models.py
@@ -210,10 +210,3 @@ class EventExportRow(BaseModel):
error: str
-@dataclass
-class EventAggregationResult:
- """Result of event aggregation."""
-
- results: list[dict[str, Any]]
- pipeline: list[dict[str, Any]]
- execution_time_ms: float | None = None
diff --git a/backend/app/domain/execution/__init__.py b/backend/app/domain/execution/__init__.py
index b1fddd13..660d5443 100644
--- a/backend/app/domain/execution/__init__.py
+++ b/backend/app/domain/execution/__init__.py
@@ -1,9 +1,11 @@
from .exceptions import (
EventPublishError,
ExecutionNotFoundError,
+ ExecutionTerminalError,
RuntimeNotSupportedError,
)
from .models import (
+ CancelResult,
DomainExecution,
DomainExecutionCreate,
DomainExecutionUpdate,
@@ -13,10 +15,12 @@
)
__all__ = [
+ "CancelResult",
"DomainExecution",
"DomainExecutionCreate",
"DomainExecutionUpdate",
"ExecutionResultDomain",
+ "ExecutionTerminalError",
"LanguageInfoDomain",
"ResourceLimitsDomain",
"RuntimeNotSupportedError",
diff --git a/backend/app/domain/execution/exceptions.py b/backend/app/domain/execution/exceptions.py
index 25e0ceb5..ce2b42da 100644
--- a/backend/app/domain/execution/exceptions.py
+++ b/backend/app/domain/execution/exceptions.py
@@ -1,4 +1,5 @@
-from app.domain.exceptions import InfrastructureError, NotFoundError, ValidationError
+from app.domain.enums import ExecutionStatus
+from app.domain.exceptions import InfrastructureError, InvalidStateError, NotFoundError, ValidationError
class ExecutionNotFoundError(NotFoundError):
@@ -17,6 +18,15 @@ def __init__(self, lang: str, version: str) -> None:
super().__init__(f"Runtime not supported: {lang} {version}")
+class ExecutionTerminalError(InvalidStateError):
+ """Raised when attempting to cancel an execution in a terminal state."""
+
+ def __init__(self, execution_id: str, status: ExecutionStatus) -> None:
+ self.execution_id = execution_id
+ self.status = status
+ super().__init__(f"Cannot cancel execution in {status} state")
+
+
class EventPublishError(InfrastructureError):
"""Raised when event publishing fails."""
diff --git a/backend/app/domain/execution/models.py b/backend/app/domain/execution/models.py
index 335c0ec0..1e03751c 100644
--- a/backend/app/domain/execution/models.py
+++ b/backend/app/domain/execution/models.py
@@ -1,14 +1,23 @@
from __future__ import annotations
+from dataclasses import dataclass
from datetime import datetime, timezone
from uuid import uuid4
from pydantic import BaseModel, ConfigDict, Field
-from app.domain.enums import ExecutionErrorType, ExecutionStatus
+from app.domain.enums import CancelStatus, ExecutionErrorType, ExecutionStatus
from app.domain.events import EventMetadata, ResourceUsageDomain
+@dataclass
+class CancelResult:
+ execution_id: str
+ status: CancelStatus
+ message: str
+ event_id: str | None
+
+
class DomainExecution(BaseModel):
model_config = ConfigDict(from_attributes=True)
diff --git a/backend/app/domain/replay/models.py b/backend/app/domain/replay/models.py
index 2aedbb7c..eb9d9246 100644
--- a/backend/app/domain/replay/models.py
+++ b/backend/app/domain/replay/models.py
@@ -108,6 +108,8 @@ def to_mongo_query(self) -> dict[str, Any]:
class ReplayConfig(BaseModel):
+ model_config = ConfigDict(from_attributes=True)
+
replay_type: ReplayType
target: ReplayTarget = ReplayTarget.KAFKA
filter: ReplayFilter = Field(default_factory=ReplayFilter)
diff --git a/backend/app/main.py b/backend/app/main.py
index 28728095..d11242f2 100644
--- a/backend/app/main.py
+++ b/backend/app/main.py
@@ -8,7 +8,6 @@
dlq,
events,
execution,
- grafana_alerts,
health,
notifications,
replay,
@@ -124,7 +123,6 @@ def create_app(settings: Settings | None = None) -> FastAPI:
app.include_router(user_settings.router, prefix=settings.API_V1_STR)
app.include_router(notifications.router, prefix=settings.API_V1_STR)
app.include_router(saga.router, prefix=settings.API_V1_STR)
- app.include_router(grafana_alerts.router, prefix=settings.API_V1_STR)
logger.info("All routers configured")
diff --git a/backend/app/schemas_pydantic/dlq.py b/backend/app/schemas_pydantic/dlq.py
index d1a8137a..b16c4fa4 100644
--- a/backend/app/schemas_pydantic/dlq.py
+++ b/backend/app/schemas_pydantic/dlq.py
@@ -3,28 +3,13 @@
from pydantic import BaseModel, ConfigDict
from app.dlq import (
- AgeStatistics,
DLQMessageStatus,
DLQRetryResult,
- EventTypeStatistic,
RetryStrategy,
- TopicStatistic,
)
from app.domain.events import DomainEvent
-class DLQStats(BaseModel):
- """Statistics for the Dead Letter Queue."""
-
- model_config = ConfigDict(from_attributes=True)
-
- by_status: dict[DLQMessageStatus, int]
- by_topic: list[TopicStatistic]
- by_event_type: list[EventTypeStatistic]
- age_stats: AgeStatistics
- timestamp: datetime
-
-
class DLQMessageResponse(BaseModel):
"""Response model for a DLQ message. Mirrors DLQMessage for direct model_validate."""
diff --git a/backend/app/schemas_pydantic/events.py b/backend/app/schemas_pydantic/events.py
index b02533ed..cf57bfaf 100644
--- a/backend/app/schemas_pydantic/events.py
+++ b/backend/app/schemas_pydantic/events.py
@@ -5,7 +5,7 @@
from pydantic import BaseModel, ConfigDict, Field, field_validator
from app.domain.enums import Environment, EventType, SortOrder
-from app.domain.events import ContainerStatusInfo, DomainEvent
+from app.domain.events import DomainEvent
class EventTypeCountSchema(BaseModel):
@@ -95,13 +95,6 @@ def validate_sort_field(cls, v: str) -> str:
return v
-class EventAggregationRequest(BaseModel):
- """Request model for event aggregation queries."""
-
- pipeline: list[dict[str, Any]] = Field(..., description="MongoDB aggregation pipeline")
- limit: int = Field(100, ge=1, le=1000)
-
-
class PublishEventRequest(BaseModel):
"""Request model for publishing events."""
@@ -152,35 +145,6 @@ class EventBase(BaseModel):
)
-class ExecutionEventPayload(BaseModel):
- """Common payload for execution-related events in API responses."""
-
- execution_id: str
- user_id: str
- status: str | None = None
- script: str | None = None
- language: str | None = None
- language_version: str | None = None
- output: str | None = None
- errors: str | None = None
- exit_code: int | None = None
- duration_seconds: float | None = None
-
-
-class PodEventPayload(BaseModel):
- """Common payload for pod-related events in API responses."""
-
- pod_name: str
- namespace: str
- execution_id: str
- phase: str | None = None
- container_statuses: list[ContainerStatusInfo] | None = None
- node_name: str | None = None
- pod_ip: str | None = None
- reason: str | None = None
- message: str | None = None
-
-
class EventInDB(EventBase):
"""Event as stored in database."""
diff --git a/backend/app/schemas_pydantic/execution.py b/backend/app/schemas_pydantic/execution.py
index 1854b87e..9b8bf7e9 100644
--- a/backend/app/schemas_pydantic/execution.py
+++ b/backend/app/schemas_pydantic/execution.py
@@ -136,7 +136,7 @@ class ExampleScripts(BaseModel):
class CancelExecutionRequest(BaseModel):
"""Model for cancelling an execution."""
- reason: str | None = Field(None, description="Reason for cancellation")
+ reason: str = Field("User requested cancellation", description="Reason for cancellation")
class RetryExecutionRequest(BaseModel):
@@ -164,6 +164,8 @@ class CancelResponse(BaseModel):
message: str
event_id: str | None = Field(None, description="Event ID for the cancellation event, if published")
+ model_config = ConfigDict(from_attributes=True)
+
class DeleteResponse(BaseModel):
"""Model for execution deletion response."""
diff --git a/backend/app/schemas_pydantic/grafana.py b/backend/app/schemas_pydantic/grafana.py
deleted file mode 100644
index 4be78cdc..00000000
--- a/backend/app/schemas_pydantic/grafana.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from __future__ import annotations
-
-from pydantic import BaseModel, Field
-
-
-class GrafanaAlertItem(BaseModel):
- status: str | None = None
- labels: dict[str, str] = Field(default_factory=dict)
- annotations: dict[str, str] = Field(default_factory=dict)
- valueString: str | None = None
-
-
-class GrafanaWebhook(BaseModel):
- status: str | None = None
- receiver: str | None = None
- alerts: list[GrafanaAlertItem] = Field(default_factory=list)
- groupLabels: dict[str, str] = Field(default_factory=dict)
- commonLabels: dict[str, str] = Field(default_factory=dict)
- commonAnnotations: dict[str, str] = Field(default_factory=dict)
-
-
-class AlertResponse(BaseModel):
- message: str
- alerts_received: int
- alerts_processed: int
- errors: list[str] = Field(default_factory=list)
diff --git a/backend/app/schemas_pydantic/health_dashboard.py b/backend/app/schemas_pydantic/health_dashboard.py
deleted file mode 100644
index 6767aafd..00000000
--- a/backend/app/schemas_pydantic/health_dashboard.py
+++ /dev/null
@@ -1,283 +0,0 @@
-from datetime import datetime
-
-from pydantic import BaseModel, ConfigDict, Field
-
-from app.domain.enums import AlertSeverity
-
-
-class HealthAlert(BaseModel):
- """Health alert information."""
-
- id: str = Field(..., description="Unique alert identifier")
- severity: AlertSeverity = Field(..., description="Alert severity level")
- service: str = Field(..., description="Service name that triggered the alert")
- status: str = Field(..., description="Current health status")
- message: str = Field(..., description="Alert message")
- timestamp: datetime = Field(..., description="Alert timestamp")
- duration_ms: float = Field(..., description="Check duration in milliseconds")
- error: str | None = Field(None, description="Error details if any")
-
-
-class HealthMetricsSummary(BaseModel):
- """Summary of health metrics for dashboard display"""
-
- total_checks: int
- healthy_checks: int
- failed_checks: int
- avg_check_duration_ms: float
- total_failures_24h: int
- uptime_percentage_24h: float
-
-
-class ServiceMetrics(BaseModel):
- """Detailed metrics for a specific service"""
-
- service_name: str
- check_count_24h: int
- failure_count_24h: int
- avg_duration_ms: float
- p95_duration_ms: float
- p99_duration_ms: float
- consecutive_failures: int
- last_failure_time: datetime | None = Field(None, description="Last failure timestamp")
- failure_reasons: dict[str, int]
-
-
-class HealthTrend(BaseModel):
- """Health trend data point"""
-
- timestamp: datetime = Field(..., description="Trend data timestamp")
- status: str
- healthy_count: int
- unhealthy_count: int
- degraded_count: int
-
-
-class ServiceHealth(BaseModel):
- """Service health information"""
-
- name: str
- status: str
- uptime_percentage: float
- last_check: datetime = Field(..., description="Last health check timestamp")
- message: str
- critical: bool
-
-
-class HealthDashboardResponse(BaseModel):
- """Complete health dashboard response"""
-
- overall_status: str
- last_updated: datetime = Field(..., description="Dashboard last update timestamp")
- services: list[ServiceHealth]
- statistics: dict[str, int]
- alerts: list[HealthAlert]
- trends: list[HealthTrend]
-
-
-class SimpleHealthStatus(BaseModel):
- """Simple health status response for public endpoint."""
-
- status: str = Field(..., description="Health status: 'healthy' or 'unhealthy'")
-
-
-class HealthStatistics(BaseModel):
- """Health check statistics."""
-
- total_checks: int
- healthy: int
- degraded: int
- unhealthy: int
- unknown: int
-
-
-class CategoryServices(BaseModel):
- """Services within a health category."""
-
- status: str
- message: str
- duration_ms: float
- details: dict[str, object] = Field(default_factory=dict)
-
-
-class DetailedHealthStatus(BaseModel):
- """Detailed health status with all categories and statistics."""
-
- timestamp: str = Field(..., description="ISO timestamp of health check")
- overall_status: str = Field(..., description="Overall system health status")
- categories: dict[str, dict[str, CategoryServices]] = Field(
- default_factory=dict, description="Health checks organized by category"
- )
- statistics: HealthStatistics
-
-
-class HealthCheckConfig(BaseModel):
- """Health check configuration details."""
-
- type: str | None = None
- critical: bool | None = None
- interval_seconds: float | None = None
- timeout_seconds: float | None = None
- failure_threshold: int | None = None
-
-
-class HealthCheckState(BaseModel):
- """Current state of health check."""
-
- consecutive_failures: int
- consecutive_successes: int
-
-
-class ServiceHealthDetails(BaseModel):
- """Detailed health information for a specific service."""
-
- name: str
- status: str
- message: str
- details: dict[str, object] = Field(default_factory=dict)
- duration_ms: float
- timestamp: datetime
- error: str | None = None
- check_config: HealthCheckConfig
- state: HealthCheckState
-
-
-class CategoryHealthStatistics(BaseModel):
- """Statistics for a health category."""
-
- total: int
- healthy: int
- degraded: int
- unhealthy: int
-
-
-class CategoryHealthResponse(BaseModel):
- """Health information for a specific category."""
-
- category: str
- status: str
- services: dict[str, CategoryServices] = Field(default_factory=dict)
- statistics: CategoryHealthStatistics
-
-
-class DependencyNode(BaseModel):
- """Service dependency graph node."""
-
- id: str
- label: str
- status: str
- critical: bool
- message: str
-
-
-class DependencyEdge(BaseModel):
- """Service dependency graph edge."""
-
- from_service: str = Field(..., alias="from")
- to_service: str = Field(..., alias="to")
- critical: bool
-
- model_config = ConfigDict(populate_by_name=True)
-
-
-class DependencyGraph(BaseModel):
- """Service dependency graph structure."""
-
- nodes: list[DependencyNode]
- edges: list[DependencyEdge]
-
-
-class ServiceImpactAnalysis(BaseModel):
- """Impact analysis for an unhealthy service."""
-
- status: str
- affected_services: list[str]
- is_critical: bool
-
-
-class ServiceDependenciesResponse(BaseModel):
- """Service dependencies and impact analysis."""
-
- dependency_graph: DependencyGraph
- impact_analysis: dict[str, ServiceImpactAnalysis]
- total_services: int
- healthy_services: int
- critical_services_down: int
-
-
-class HealthCheckTriggerResponse(BaseModel):
- """Response from manually triggered health check."""
-
- service: str
- status: str
- message: str
- duration_ms: float
- timestamp: datetime
- details: dict[str, object] = Field(default_factory=dict)
- error: str | None = None
- is_critical: bool
-
-
-class ServiceHistoryDataPoint(BaseModel):
- """Single data point in service history."""
-
- timestamp: datetime
- status: str
- duration_ms: float
- healthy: bool
-
-
-class ServiceHistorySummary(BaseModel):
- """Summary statistics for service history."""
-
- uptime_percentage: float
- total_checks: int
- healthy_checks: int
- failure_count: int
-
-
-class ServiceHistoryResponse(BaseModel):
- """Historical health data for a service."""
-
- service_name: str
- time_range_hours: int
- data_points: list[ServiceHistoryDataPoint]
- summary: ServiceHistorySummary
-
-
-class SystemMetrics(BaseModel):
- """System-level metrics for real-time status."""
-
- mongodb_connections: int
- mongodb_ops_per_sec: int
- kafka_total_lag: int
- active_health_checks: int
- failing_checks: int
-
-
-class ServiceRealtimeStatus(BaseModel):
- """Real-time status for a single service."""
-
- status: str
- message: str
- duration_ms: float
- last_check: datetime
- details: dict[str, object] = Field(default_factory=dict)
-
-
-class LastIncident(BaseModel):
- """Information about the last system incident."""
-
- time: datetime | None = None
- service: str | None = None
- duration_minutes: int | None = None
-
-
-class RealtimeStatusResponse(BaseModel):
- """Real-time health status with live metrics."""
-
- timestamp: datetime
- overall_status: str
- services: dict[str, ServiceRealtimeStatus]
- system_metrics: SystemMetrics
- last_incident: LastIncident
diff --git a/backend/app/schemas_pydantic/replay.py b/backend/app/schemas_pydantic/replay.py
index 3728d834..2fc404cf 100644
--- a/backend/app/schemas_pydantic/replay.py
+++ b/backend/app/schemas_pydantic/replay.py
@@ -33,14 +33,22 @@ class ReplayResponse(BaseModel):
message: str
+class SessionConfigSummary(BaseModel):
+ """Lightweight config included in session listings."""
+
+ model_config = ConfigDict(from_attributes=True)
+
+ replay_type: ReplayType
+ target: ReplayTarget
+
+
class SessionSummary(BaseModel):
"""Summary information for replay sessions"""
model_config = ConfigDict(from_attributes=True)
session_id: str
- replay_type: ReplayType
- target: ReplayTarget
+ config: SessionConfigSummary
status: ReplayStatus
total_events: int
replayed_events: int
diff --git a/backend/app/schemas_pydantic/user.py b/backend/app/schemas_pydantic/user.py
index d89d706c..0326e675 100644
--- a/backend/app/schemas_pydantic/user.py
+++ b/backend/app/schemas_pydantic/user.py
@@ -54,7 +54,9 @@ class UserResponse(UserBase):
"""User model for API responses (without password)"""
user_id: str
- is_superuser: bool = False
+ role: UserRole
+ is_active: bool
+ is_superuser: bool
created_at: datetime
updated_at: datetime
# Rate limit summary fields (optional, populated by admin endpoints)
@@ -117,21 +119,12 @@ class LoginResponse(BaseModel):
message: str
username: str
- role: str
+ role: UserRole
csrf_token: str
model_config = ConfigDict(from_attributes=True)
-class TokenValidationResponse(BaseModel):
- """Response model for token validation"""
-
- valid: bool
- username: str
- role: str
- csrf_token: str
-
- model_config = ConfigDict(from_attributes=True)
class DeleteUserResponse(BaseModel):
diff --git a/backend/app/services/event_service.py b/backend/app/services/event_service.py
index 62d60f0c..db039469 100644
--- a/backend/app/services/event_service.py
+++ b/backend/app/services/event_service.py
@@ -6,7 +6,6 @@
from app.domain.events import (
ArchivedEvent,
DomainEvent,
- EventAggregationResult,
EventFilter,
EventListResult,
EventReplayInfo,
@@ -182,30 +181,6 @@ async def get_event(
return None
return event
- async def aggregate_events(
- self,
- user_id: str,
- user_role: UserRole,
- pipeline: list[dict[str, Any]],
- limit: int = 100,
- ) -> EventAggregationResult:
- user_filter = self._build_user_filter(user_id, user_role)
- new_pipeline = list(pipeline)
- if user_filter:
- if new_pipeline and "$match" in new_pipeline[0]:
- new_pipeline[0]["$match"] = {"$and": [new_pipeline[0]["$match"], user_filter]}
- else:
- new_pipeline.insert(0, {"$match": user_filter})
- return await self.repository.aggregate_events(new_pipeline, limit=limit)
-
- async def list_event_types(
- self,
- user_id: str,
- user_role: UserRole,
- ) -> list[str]:
- match = self._build_user_filter(user_id, user_role)
- return await self.repository.list_event_types(match=match)
-
async def delete_event_with_archival(
self,
event_id: str,
diff --git a/backend/app/services/execution_service.py b/backend/app/services/execution_service.py
index c3bf44ce..43eaacb4 100644
--- a/backend/app/services/execution_service.py
+++ b/backend/app/services/execution_service.py
@@ -1,29 +1,34 @@
import logging
from contextlib import contextmanager
-from datetime import datetime
+from datetime import datetime, timezone
from time import time
from typing import Any, Generator
+from uuid import uuid4
from app.core.correlation import CorrelationContext
from app.core.metrics import ExecutionMetrics
-from app.db.repositories import EventRepository, ExecutionRepository
-from app.domain.enums import EventType, ExecutionStatus, QueuePriority
+from app.db.repositories import ExecutionRepository
+from app.domain.enums import CancelStatus, EventType, ExecutionStatus, QueuePriority
from app.domain.events import (
- DomainEvent,
+ BaseEvent,
EventMetadata,
ExecutionCancelledEvent,
ExecutionRequestedEvent,
)
-from app.domain.exceptions import InfrastructureError
+from app.domain.exceptions import ConflictError, InfrastructureError
from app.domain.execution import (
+ CancelResult,
DomainExecution,
DomainExecutionCreate,
ExecutionNotFoundError,
ExecutionResultDomain,
+ ExecutionTerminalError,
ResourceLimitsDomain,
)
+from app.domain.idempotency import IdempotencyStatus, KeyStrategy
from app.events.core import UnifiedProducer
from app.runtime_registry import RUNTIME_REGISTRY
+from app.services.idempotency import IdempotencyManager
from app.settings import Settings
@@ -40,10 +45,10 @@ def __init__(
self,
execution_repo: ExecutionRepository,
producer: UnifiedProducer,
- event_repository: EventRepository,
settings: Settings,
logger: logging.Logger,
execution_metrics: ExecutionMetrics,
+ idempotency_manager: IdempotencyManager,
) -> None:
"""
Initialize execution service.
@@ -51,17 +56,17 @@ def __init__(
Args:
execution_repo: Repository for execution data persistence.
producer: Kafka producer for publishing events.
- event_repository: Repository for event queries.
settings: Application settings.
logger: Logger instance.
execution_metrics: Metrics for tracking execution operations.
+ idempotency_manager: Manager for HTTP idempotency.
"""
self.execution_repo = execution_repo
self.producer = producer
- self.event_repository = event_repository
self.settings = settings
self.logger = logger
self.metrics = execution_metrics
+ self.idempotency_manager = idempotency_manager
@contextmanager
def _track_active_execution(self) -> Generator[None, None, None]: # noqa: D401
@@ -173,7 +178,7 @@ async def execute_script(
self.logger.info(
"Created execution record",
extra={
- "execution_id": str(created_execution.execution_id),
+ "execution_id": created_execution.execution_id,
"lang": lang,
"lang_version": lang_version,
"user_id": user_id,
@@ -210,7 +215,7 @@ async def execute_script(
self.metrics.record_error(type(e).__name__)
await self._update_execution_error(
created_execution.execution_id,
- f"Failed to submit execution: {str(e)}",
+ f"Failed to submit execution: {e}",
)
raise InfrastructureError("Failed to submit execution request") from e
@@ -221,13 +226,176 @@ async def execute_script(
self.logger.info(
"Script execution submitted successfully",
extra={
- "execution_id": str(created_execution.execution_id),
+ "execution_id": created_execution.execution_id,
"status": created_execution.status,
"duration_seconds": duration,
},
)
return created_execution
+ async def cancel_execution(
+ self,
+ execution_id: str,
+ current_status: ExecutionStatus,
+ user_id: str,
+ reason: str = "User requested cancellation",
+ ) -> CancelResult:
+ """
+ Cancel a running or queued execution.
+
+ Args:
+ execution_id: UUID of the execution.
+ current_status: Current status of the execution.
+ user_id: User requesting cancellation.
+ reason: Cancellation reason.
+
+ Returns:
+ CancelResult with status and event info.
+
+ Raises:
+ ExecutionTerminalError: If execution is in a terminal state.
+ """
+ terminal_states = {
+ ExecutionStatus.COMPLETED,
+ ExecutionStatus.FAILED,
+ ExecutionStatus.TIMEOUT,
+ ExecutionStatus.ERROR,
+ }
+
+ if current_status in terminal_states:
+ raise ExecutionTerminalError(execution_id, current_status)
+
+ if current_status == ExecutionStatus.CANCELLED:
+ return CancelResult(
+ execution_id=execution_id,
+ status=CancelStatus.ALREADY_CANCELLED,
+ message="Execution was already cancelled",
+ event_id=None,
+ )
+
+ metadata = self._create_event_metadata(user_id=user_id)
+ event = ExecutionCancelledEvent(
+ execution_id=execution_id,
+ aggregate_id=execution_id,
+ reason=reason,
+ cancelled_by=user_id,
+ metadata=metadata,
+ )
+
+ await self.producer.produce(event_to_produce=event, key=execution_id)
+
+ self.logger.info(
+ "Published cancellation event",
+ extra={"execution_id": execution_id, "event_id": event.event_id},
+ )
+
+ return CancelResult(
+ execution_id=execution_id,
+ status=CancelStatus.CANCELLATION_REQUESTED,
+ message="Cancellation request submitted",
+ event_id=event.event_id,
+ )
+
+ async def execute_script_idempotent(
+ self,
+ script: str,
+ user_id: str,
+ *,
+ client_ip: str | None,
+ user_agent: str | None,
+ lang: str = "python",
+ lang_version: str = "3.11",
+ idempotency_key: str | None = None,
+ ) -> DomainExecution:
+ """
+ Execute a script with optional idempotency support.
+
+ Args:
+ script: The code to execute.
+ user_id: ID of the user requesting execution.
+ client_ip: Client IP address.
+ user_agent: User agent string.
+ lang: Programming language.
+ lang_version: Language version.
+ idempotency_key: Optional HTTP idempotency key.
+
+ Returns:
+ DomainExecution record.
+ """
+ if not idempotency_key:
+ return await self.execute_script(
+ script=script,
+ lang=lang,
+ lang_version=lang_version,
+ user_id=user_id,
+ client_ip=client_ip,
+ user_agent=user_agent,
+ )
+
+ pseudo_event = BaseEvent(
+ event_id=str(uuid4()),
+ event_type=EventType.EXECUTION_REQUESTED,
+ timestamp=datetime.now(timezone.utc),
+ metadata=EventMetadata(
+ user_id=user_id,
+ correlation_id=str(uuid4()),
+ service_name="api",
+ service_version="1.0.0",
+ ),
+ )
+ custom_key = f"http:{user_id}:{idempotency_key}"
+
+ idempotency_result = await self.idempotency_manager.check_and_reserve(
+ event=pseudo_event,
+ key_strategy=KeyStrategy.CUSTOM,
+ custom_key=custom_key,
+ ttl_seconds=86400,
+ )
+
+ if idempotency_result.is_duplicate:
+ if not idempotency_result.has_cached_result:
+ raise ConflictError(
+ f"Duplicate request '{idempotency_key}' is still being processed"
+ if idempotency_result.status == IdempotencyStatus.PROCESSING
+ else f"Previous request '{idempotency_key}' failed"
+ )
+ cached_json = await self.idempotency_manager.get_cached_json(
+ event=pseudo_event,
+ key_strategy=KeyStrategy.CUSTOM,
+ custom_key=custom_key,
+ )
+ if not cached_json:
+ raise ConflictError(f"Cached result for '{idempotency_key}' is no longer available")
+ return DomainExecution.model_validate_json(cached_json)
+
+ try:
+ exec_result = await self.execute_script(
+ script=script,
+ lang=lang,
+ lang_version=lang_version,
+ user_id=user_id,
+ client_ip=client_ip,
+ user_agent=user_agent,
+ )
+
+ await self.idempotency_manager.mark_completed_with_json(
+ event=pseudo_event,
+ cached_json=exec_result.model_dump_json(),
+ key_strategy=KeyStrategy.CUSTOM,
+ custom_key=custom_key,
+ )
+
+ return exec_result
+
+ except Exception as e:
+ await self.idempotency_manager.mark_failed(
+ event=pseudo_event,
+ error=str(e),
+ key_strategy=KeyStrategy.CUSTOM,
+ custom_key=custom_key,
+ )
+ raise
+
async def _update_execution_error(self, execution_id: str, error_message: str) -> None:
result = ExecutionResultDomain(
execution_id=execution_id,
@@ -277,39 +445,6 @@ async def get_execution_result(self, execution_id: str) -> DomainExecution:
return execution
- async def get_execution_events(
- self,
- execution_id: str,
- event_types: list[EventType] | None = None,
- limit: int = 100,
- ) -> list[DomainEvent]:
- """
- Get all events for an execution from the event store.
-
- Args:
- execution_id: UUID of the execution.
- event_types: Filter by specific event types.
- limit: Maximum number of events to return.
-
- Returns:
- List of events for the execution.
- """
- result = await self.event_repository.get_execution_events(
- execution_id=execution_id, event_types=event_types, limit=limit,
- )
- events = result.events
-
- self.logger.debug(
- f"Retrieved {len(events)} events for execution {execution_id}",
- extra={
- "execution_id": execution_id,
- "event_count": len(events),
- "event_types": event_types,
- },
- )
-
- return events
-
async def get_user_executions(
self,
user_id: str,
@@ -344,7 +479,7 @@ async def get_user_executions(
self.logger.debug(
f"Retrieved {len(executions)} executions for user",
extra={
- "user_id": str(user_id),
+ "user_id": user_id,
"filters": {k: v for k, v in query.items() if k != "user_id"},
"limit": limit,
"skip": skip,
@@ -398,7 +533,7 @@ def _build_user_query(
Returns:
MongoDB query dictionary.
"""
- query: dict[str, Any] = {"user_id": str(user_id)}
+ query: dict[str, Any] = {"user_id": user_id}
if status:
query["status"] = status
@@ -449,7 +584,11 @@ async def _publish_deletion_event(self, execution_id: str) -> None:
metadata = self._create_event_metadata()
event = ExecutionCancelledEvent(
- execution_id=execution_id, reason="user_requested", cancelled_by=metadata.user_id, metadata=metadata
+ execution_id=execution_id,
+ aggregate_id=execution_id,
+ reason="user_requested",
+ cancelled_by=metadata.user_id,
+ metadata=metadata,
)
await self.producer.produce(event_to_produce=event, key=execution_id)
@@ -458,7 +597,7 @@ async def _publish_deletion_event(self, execution_id: str) -> None:
"Published cancellation event",
extra={
"execution_id": execution_id,
- "event_id": str(event.event_id),
+ "event_id": event.event_id,
},
)
@@ -501,7 +640,7 @@ def _build_stats_query(
query: dict[str, Any] = {}
if user_id:
- query["user_id"] = str(user_id)
+ query["user_id"] = user_id
start_time, end_time = time_range
if start_time or end_time:
diff --git a/backend/app/services/grafana_alert_processor.py b/backend/app/services/grafana_alert_processor.py
deleted file mode 100644
index 1c6cd59a..00000000
--- a/backend/app/services/grafana_alert_processor.py
+++ /dev/null
@@ -1,151 +0,0 @@
-"""Grafana alert processing service."""
-
-import logging
-from typing import Any
-
-from app.domain.enums import NotificationSeverity, UserRole
-from app.schemas_pydantic.grafana import GrafanaAlertItem, GrafanaWebhook
-from app.services.notification_service import NotificationService
-
-
-class GrafanaAlertProcessor:
- """Processes Grafana alerts with reduced complexity."""
-
- SEVERITY_MAPPING = {
- "critical": NotificationSeverity.HIGH,
- "error": NotificationSeverity.HIGH,
- "warning": NotificationSeverity.MEDIUM,
- "info": NotificationSeverity.LOW,
- }
-
- RESOLVED_STATUSES = {"ok", "resolved"}
- DEFAULT_SEVERITY = "warning"
- DEFAULT_TITLE = "Grafana Alert"
- DEFAULT_MESSAGE = "Alert triggered"
-
- def __init__(self, notification_service: NotificationService, logger: logging.Logger) -> None:
- """Initialize the processor with required services."""
- self.notification_service = notification_service
- self.logger = logger
- self.logger.info("GrafanaAlertProcessor initialized")
-
- @classmethod
- def extract_severity(cls, alert: GrafanaAlertItem, webhook: GrafanaWebhook) -> str:
- """Extract severity from alert or webhook labels."""
- alert_severity = (alert.labels or {}).get("severity")
- webhook_severity = (webhook.commonLabels or {}).get("severity")
- return (alert_severity or webhook_severity or cls.DEFAULT_SEVERITY).lower()
-
- @classmethod
- def map_severity(cls, severity_str: str, alert_status: str | None) -> NotificationSeverity:
- """Map string severity to enum, considering alert status."""
- if alert_status and alert_status.lower() in cls.RESOLVED_STATUSES:
- return NotificationSeverity.LOW
- return cls.SEVERITY_MAPPING.get(severity_str, NotificationSeverity.MEDIUM)
-
- @classmethod
- def extract_title(cls, alert: GrafanaAlertItem) -> str:
- """Extract title from alert labels or annotations."""
- return (alert.labels or {}).get("alertname") or (alert.annotations or {}).get("title") or cls.DEFAULT_TITLE
-
- @classmethod
- def build_message(cls, alert: GrafanaAlertItem) -> str:
- """Build notification message from alert annotations."""
- annotations = alert.annotations or {}
- summary = annotations.get("summary")
- description = annotations.get("description")
-
- parts = [p for p in [summary, description] if p]
- if parts:
- return "\n\n".join(parts)
- return summary or description or cls.DEFAULT_MESSAGE
-
- @classmethod
- def build_metadata(cls, alert: GrafanaAlertItem, webhook: GrafanaWebhook, severity: str) -> dict[str, Any]:
- """Build metadata dictionary for the notification."""
- return {
- "grafana_status": alert.status or webhook.status,
- "severity": severity,
- **(webhook.commonLabels or {}),
- **(alert.labels or {}),
- }
-
- async def process_single_alert(
- self,
- alert: GrafanaAlertItem,
- webhook: GrafanaWebhook,
- correlation_id: str,
- ) -> tuple[bool, str | None]:
- """Process a single Grafana alert.
-
- Args:
- alert: The Grafana alert to process
- webhook: The webhook payload containing common data
- correlation_id: Correlation ID for tracing
-
- Returns:
- Tuple of (success, error_message)
- """
- try:
- severity_str = self.extract_severity(alert, webhook)
- severity = self.map_severity(severity_str, alert.status)
- title = self.extract_title(alert)
- message = self.build_message(alert)
- metadata = self.build_metadata(alert, webhook, severity_str)
-
- await self.notification_service.create_system_notification(
- title=title,
- message=message,
- severity=severity,
- tags=["external_alert", "grafana", "entity:external_alert"],
- metadata=metadata,
- target_roles=[UserRole.ADMIN],
- )
- return True, None
-
- except Exception as e:
- error_msg = f"Failed to process Grafana alert: {e}"
- self.logger.error(error_msg, extra={"correlation_id": correlation_id}, exc_info=True)
- return False, error_msg
-
- async def process_webhook(self, webhook_payload: GrafanaWebhook, correlation_id: str) -> tuple[int, list[str]]:
- """Process all alerts in a Grafana webhook.
-
- Args:
- webhook_payload: The Grafana webhook payload
- correlation_id: Correlation ID for tracing
-
- Returns:
- Tuple of (processed_count, errors)
- """
- alerts = webhook_payload.alerts or []
- errors: list[str] = []
- processed_count = 0
-
- self.logger.info(
- "Processing Grafana webhook",
- extra={
- "correlation_id": correlation_id,
- "status": webhook_payload.status,
- "alerts_count": len(alerts),
- },
- )
-
- for alert in alerts:
- success, error_msg = await self.process_single_alert(alert, webhook_payload, correlation_id)
- if success:
- processed_count += 1
- elif error_msg:
- errors.append(error_msg)
-
- self.logger.info(
- "Grafana webhook processing completed",
- extra={
- "correlation_id": correlation_id,
- "alerts_received": len(alerts),
- "alerts_processed": processed_count,
- "errors_count": len(errors),
- },
- )
-
- return processed_count, errors
diff --git a/backend/app/services/idempotency/idempotency_manager.py b/backend/app/services/idempotency/idempotency_manager.py
index aa56f220..4767f693 100644
--- a/backend/app/services/idempotency/idempotency_manager.py
+++ b/backend/app/services/idempotency/idempotency_manager.py
@@ -235,8 +235,9 @@ async def mark_completed_with_json(
async def get_cached_json(
self, event: BaseEvent, key_strategy: KeyStrategy, custom_key: str | None, fields: set[str] | None = None
- ) -> str:
+ ) -> str | None:
full_key = self._generate_key(event, key_strategy, custom_key, fields)
existing = await self._repo.find_by_key(full_key)
- assert existing and existing.result_json is not None, "Invariant: cached result must exist when requested"
+ if not existing or existing.result_json is None:
+ return None
return existing.result_json
diff --git a/backend/app/services/kafka_event_service.py b/backend/app/services/kafka_event_service.py
index 018227f8..4baa5853 100644
--- a/backend/app/services/kafka_event_service.py
+++ b/backend/app/services/kafka_event_service.py
@@ -8,7 +8,7 @@
from app.core.correlation import CorrelationContext
from app.core.metrics import EventMetrics
-from app.domain.enums import EventType
+from app.domain.enums import EventType, ExecutionStatus
from app.domain.events import DomainEvent, DomainEventAdapter, EventMetadata
from app.events.core import UnifiedProducer
from app.settings import Settings
@@ -85,7 +85,7 @@ async def publish_execution_event(
self,
event_type: EventType,
execution_id: str,
- status: str,
+ status: ExecutionStatus,
metadata: EventMetadata | None = None,
error_message: str | None = None,
) -> str:
diff --git a/backend/app/services/notification_service.py b/backend/app/services/notification_service.py
index a39f0c36..204cb281 100644
--- a/backend/app/services/notification_service.py
+++ b/backend/app/services/notification_service.py
@@ -502,12 +502,11 @@ async def handle_execution_failed(self, event: ExecutionFailedEvent) -> None:
metadata=event_data,
)
- async def mark_as_read(self, user_id: str, notification_id: str) -> bool:
+ async def mark_as_read(self, user_id: str, notification_id: str) -> None:
"""Mark notification as read."""
success = await self.repository.mark_as_read(notification_id, user_id)
if not success:
raise NotificationNotFoundError(notification_id)
- return True
async def get_unread_count(self, user_id: str) -> int:
"""Get count of unread notifications."""
@@ -593,12 +592,11 @@ async def get_subscriptions(self, user_id: str) -> dict[NotificationChannel, Dom
"""Get all notification subscriptions for a user."""
return await self.repository.get_all_subscriptions(user_id)
- async def delete_notification(self, user_id: str, notification_id: str) -> bool:
+ async def delete_notification(self, user_id: str, notification_id: str) -> None:
"""Delete a notification."""
deleted = await self.repository.delete_notification(str(notification_id), user_id)
if not deleted:
raise NotificationNotFoundError(notification_id)
- return deleted
async def _publish_notification_sse(self, notification: DomainNotification) -> None:
"""Publish an in-app notification to the SSE bus for realtime delivery."""
diff --git a/backend/app/services/saga/saga_service.py b/backend/app/services/saga/saga_service.py
index dc45f381..0b91fc2d 100644
--- a/backend/app/services/saga/saga_service.py
+++ b/backend/app/services/saga/saga_service.py
@@ -10,7 +10,7 @@
SagaListResult,
SagaNotFoundError,
)
-from app.schemas_pydantic.user import User
+from app.domain.user import User
from app.services.saga import SagaOrchestrator
diff --git a/backend/grafana/grafana.ini b/backend/grafana/grafana.ini
index f4208980..fcd1d650 100644
--- a/backend/grafana/grafana.ini
+++ b/backend/grafana/grafana.ini
@@ -21,6 +21,7 @@ org_role = Viewer
[auth.basic]
enabled = false
+# turn off alerting if needed; be default - off
[unified_alerting]
enabled = false
diff --git a/backend/grafana/provisioning/alerting/alerting.yml b/backend/grafana/provisioning/alerting/alerting.yml
new file mode 100644
index 00000000..9da5240f
--- /dev/null
+++ b/backend/grafana/provisioning/alerting/alerting.yml
@@ -0,0 +1,84 @@
+apiVersion: 1
+
+# Contact points define where alert notifications are sent.
+# Uncomment and configure one or more of the examples below.
+
+# contactPoints:
+# - orgId: 1
+# name: slack-notifications
+# receivers:
+# - uid: slack-receiver
+# type: slack
+# settings:
+# url: https://hooks.slack.com/services/YOUR/WEBHOOK/URL
+# recipient: "#alerts"
+# title: |
+# {{ template "default.title" . }}
+# text: |
+# {{ template "default.message" . }}
+#
+# - orgId: 1
+# name: email-notifications
+# receivers:
+# - uid: email-receiver
+# type: email
+# settings:
+# addresses: ops-team@example.com
+# singleEmail: true
+
+contactPoints: []
+
+# Notification policies control routing: which contact point receives
+# which alerts, grouping, and timing.
+
+# policies:
+# - orgId: 1
+# receiver: slack-notifications
+# group_by: ["alertname", "namespace"]
+# group_wait: 30s
+# group_interval: 5m
+# repeat_interval: 4h
+
+policies: []
+
+# Alert rules query data sources and fire when thresholds are breached.
+# The example below queries Victoria Metrics for a high HTTP error rate.
+
+# groups:
+# - orgId: 1
+# name: backend-alerts
+# folder: Integr8sCode
+# interval: 1m
+# rules:
+# - uid: high-error-rate
+# title: High HTTP 5xx Error Rate
+# condition: C
+# data:
+# - refId: A
+# relativeTimeRange:
+# from: 300
+# to: 0
+# datasourceUid: victoria-metrics
+# model:
+# expr: >
+# sum(rate(http_requests_total{status=~"5.."}[5m]))
+# / sum(rate(http_requests_total[5m])) * 100
+# intervalMs: 15000
+# maxDataPoints: 43200
+# - refId: C
+# relativeTimeRange:
+# from: 300
+# to: 0
+# datasourceUid: __expr__
+# model:
+# type: threshold
+# conditions:
+# - evaluator:
+# type: gt
+# params: [5]
+# expression: A
+# for: 5m
+# annotations:
+# summary: "HTTP 5xx error rate is above 5%"
+# labels:
+# severity: warning
diff --git a/backend/grafana/provisioning/alerting/empty.yml b/backend/grafana/provisioning/alerting/empty.yml
deleted file mode 100644
index 6d83dd21..00000000
--- a/backend/grafana/provisioning/alerting/empty.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-apiVersion: 1
-contactPoints: []
-policies: []
\ No newline at end of file
diff --git a/backend/grafana/provisioning/notifiers/notifiers.yml b/backend/grafana/provisioning/notifiers/notifiers.yml
deleted file mode 100644
index 4fbf992a..00000000
--- a/backend/grafana/provisioning/notifiers/notifiers.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-apiVersion: 1
-notifiers: []
\ No newline at end of file
diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py
index 6245af02..2f3eb2b6 100644
--- a/backend/tests/conftest.py
+++ b/backend/tests/conftest.py
@@ -7,7 +7,8 @@
import pytest
import pytest_asyncio
import redis.asyncio as redis
-from app.domain.enums import QueuePriority
+from app.db.docs.user import UserDocument
+from app.domain.enums import QueuePriority, UserRole
from app.domain.events import EventMetadata, ExecutionRequestedEvent
from app.main import create_app
from app.settings import Settings
@@ -86,7 +87,7 @@ async def redis_client(scope: AsyncContainer) -> AsyncGenerator[redis.Redis, Non
async def _create_authenticated_client(
- app: FastAPI, username: str, email: str, password: str, role: str
+ app: FastAPI, username: str, email: str, password: str, role: UserRole
) -> httpx.AsyncClient:
"""Create and return an authenticated client with CSRF header set."""
c = httpx.AsyncClient(
@@ -100,12 +101,17 @@ async def _create_authenticated_client(
"username": username,
"email": email,
"password": password,
- "role": role,
})
# 200: created, 400: username exists, 409: email exists - all OK to proceed to login
if r.status_code not in (200, 400, 409):
pytest.fail(f"Cannot create {role} (status {r.status_code}): {r.text}")
+ # Register always creates UserRole.USER; promote via DB if needed
+ if role != UserRole.USER:
+ await UserDocument.find_one(UserDocument.username == username).set(
+ {UserDocument.role: role}
+ )
+
login_resp = await c.post("/api/v1/auth/login", data={
"username": username,
"password": password,
@@ -137,7 +143,7 @@ async def test_user(app: FastAPI) -> AsyncGenerator[httpx.AsyncClient, None]:
username=f"test_user_{uid}",
email=f"test_user_{uid}@example.com",
password="TestPass123!",
- role="user",
+ role=UserRole.USER,
)
yield c
await c.aclose()
@@ -152,7 +158,7 @@ async def test_admin(app: FastAPI) -> AsyncGenerator[httpx.AsyncClient, None]:
username=f"admin_user_{uid}",
email=f"admin_user_{uid}@example.com",
password="AdminPass123!",
- role="admin",
+ role=UserRole.ADMIN,
)
yield c
await c.aclose()
@@ -167,7 +173,7 @@ async def another_user(app: FastAPI) -> AsyncGenerator[httpx.AsyncClient, None]:
username=f"test_user_{uid}",
email=f"test_user_{uid}@example.com",
password="TestPass123!",
- role="user",
+ role=UserRole.USER,
)
yield c
await c.aclose()
diff --git a/backend/tests/e2e/app/test_main_app.py b/backend/tests/e2e/app/test_main_app.py
index c6cb4240..52c63706 100644
--- a/backend/tests/e2e/app/test_main_app.py
+++ b/backend/tests/e2e/app/test_main_app.py
@@ -47,7 +47,6 @@ def test_health_routes_registered(self, app: FastAPI) -> None:
"""Health check routes are registered."""
paths = self._get_all_paths(app)
assert "/api/v1/health/live" in paths
- assert "/api/v1/health/ready" in paths
def test_auth_routes_registered(self, app: FastAPI) -> None:
"""Authentication routes are registered."""
diff --git a/backend/tests/e2e/core/test_container.py b/backend/tests/e2e/core/test_container.py
index aeecbcc7..9380bfe9 100644
--- a/backend/tests/e2e/core/test_container.py
+++ b/backend/tests/e2e/core/test_container.py
@@ -160,7 +160,6 @@ async def test_execution_service_has_dependencies(
# Check that key dependencies are present
assert service.settings is not None
assert service.execution_repo is not None
- assert service.event_repository is not None
@pytest.mark.asyncio
async def test_security_service_uses_settings(
diff --git a/backend/tests/e2e/db/repositories/test_dlq_repository.py b/backend/tests/e2e/db/repositories/test_dlq_repository.py
index 2965cd8b..9474e847 100644
--- a/backend/tests/e2e/db/repositories/test_dlq_repository.py
+++ b/backend/tests/e2e/db/repositories/test_dlq_repository.py
@@ -74,12 +74,9 @@ async def insert_test_dlq_docs() -> None:
@pytest.mark.asyncio
-async def test_stats_list_get_and_updates(repo: DLQRepository) -> None:
+async def test_list_get_and_updates(repo: DLQRepository) -> None:
await insert_test_dlq_docs()
- stats = await repo.get_dlq_stats()
- assert isinstance(stats.by_status, dict) and len(stats.by_topic) >= 1
-
res = await repo.get_messages(limit=2)
assert res.total >= 3 and len(res.messages) <= 2
msg = await repo.get_message_by_id("id1")
diff --git a/backend/tests/e2e/dlq/test_dlq_discard.py b/backend/tests/e2e/dlq/test_dlq_discard.py
index 5c6388a1..ff4de995 100644
--- a/backend/tests/e2e/dlq/test_dlq_discard.py
+++ b/backend/tests/e2e/dlq/test_dlq_discard.py
@@ -139,23 +139,3 @@ async def test_dlq_discard_from_scheduled_status(scope: AsyncContainer) -> None:
doc = await DLQMessageDocument.find_one({"event.event_id": event_id})
assert doc is not None
assert doc.status == DLQMessageStatus.DISCARDED
-
-
-@pytest.mark.asyncio
-async def test_dlq_stats_reflect_discarded_messages(scope: AsyncContainer) -> None:
- """Test that DLQ statistics correctly count discarded messages."""
- repository: DLQRepository = await scope.get(DLQRepository)
-
- # Capture count before to ensure our discard is what increments the stat
- stats_before = await repository.get_dlq_stats()
- count_before = stats_before.by_status.get(DLQMessageStatus.DISCARDED, 0)
-
- # Create and discard a message
- event_id = f"dlq-stats-{uuid.uuid4().hex[:8]}"
- await _create_dlq_document(event_id=event_id, status=DLQMessageStatus.PENDING)
- await repository.mark_message_discarded(event_id, "test")
-
- # Get stats after - verify the count incremented by exactly 1
- stats_after = await repository.get_dlq_stats()
- count_after = stats_after.by_status.get(DLQMessageStatus.DISCARDED, 0)
- assert count_after == count_before + 1
diff --git a/backend/tests/e2e/dlq/test_dlq_retry.py b/backend/tests/e2e/dlq/test_dlq_retry.py
index 6429ac1b..78283f6f 100644
--- a/backend/tests/e2e/dlq/test_dlq_retry.py
+++ b/backend/tests/e2e/dlq/test_dlq_retry.py
@@ -117,26 +117,6 @@ async def test_dlq_retry_from_pending_status(scope: AsyncContainer) -> None:
assert doc.status == DLQMessageStatus.RETRIED
-@pytest.mark.asyncio
-async def test_dlq_stats_reflect_retried_messages(scope: AsyncContainer) -> None:
- """Test that DLQ statistics correctly count retried messages."""
- repository: DLQRepository = await scope.get(DLQRepository)
-
- # Capture count before to ensure our retry is what increments the stat
- stats_before = await repository.get_dlq_stats()
- count_before = stats_before.by_status.get(DLQMessageStatus.RETRIED, 0)
-
- # Create and retry a message
- event_id = f"dlq-stats-retry-{uuid.uuid4().hex[:8]}"
- await _create_dlq_document(event_id=event_id, status=DLQMessageStatus.SCHEDULED)
- await repository.mark_message_retried(event_id)
-
- # Get stats after - verify the count incremented by exactly 1
- stats_after = await repository.get_dlq_stats()
- count_after = stats_after.by_status.get(DLQMessageStatus.RETRIED, 0)
- assert count_after == count_before + 1
-
-
@pytest.mark.asyncio
async def test_dlq_retry_already_retried_message(scope: AsyncContainer) -> None:
"""Test that retrying an already RETRIED message still succeeds at repository level.
diff --git a/backend/tests/e2e/services/execution/test_execution_service.py b/backend/tests/e2e/services/execution/test_execution_service.py
index d17ae90d..46671a7d 100644
--- a/backend/tests/e2e/services/execution/test_execution_service.py
+++ b/backend/tests/e2e/services/execution/test_execution_service.py
@@ -4,6 +4,7 @@
from app.domain.enums import EventType, ExecutionStatus
from app.domain.execution import ResourceLimitsDomain
from app.domain.execution.exceptions import ExecutionNotFoundError
+from app.services.event_service import EventService
from app.services.execution_service import ExecutionService
from dishka import AsyncContainer
@@ -163,16 +164,16 @@ async def test_get_execution_result_not_found(
class TestGetExecutionEvents:
- """Tests for get_execution_events method."""
+ """Tests for get_events_by_aggregate via EventService."""
@pytest.mark.asyncio
async def test_get_execution_events(self, scope: AsyncContainer) -> None:
"""Get events for execution returns list."""
- svc: ExecutionService = await scope.get(ExecutionService)
+ exec_svc: ExecutionService = await scope.get(ExecutionService)
+ event_svc: EventService = await scope.get(EventService)
user_id = f"test_user_{uuid.uuid4().hex[:8]}"
- # Create execution
- exec_result = await svc.execute_script(
+ exec_result = await exec_svc.execute_script(
script="print('events test')",
user_id=user_id,
client_ip="127.0.0.1",
@@ -181,11 +182,11 @@ async def test_get_execution_events(self, scope: AsyncContainer) -> None:
lang_version="3.11",
)
- # Get events
- events = await svc.get_execution_events(exec_result.execution_id)
+ events = await event_svc.get_events_by_aggregate(
+ aggregate_id=exec_result.execution_id,
+ )
assert isinstance(events, list)
- # Should have at least EXECUTION_REQUESTED event
if events:
event_types = {e.event_type for e in events}
assert EventType.EXECUTION_REQUESTED in event_types
@@ -195,10 +196,11 @@ async def test_get_execution_events_with_filter(
self, scope: AsyncContainer
) -> None:
"""Get events filtered by type."""
- svc: ExecutionService = await scope.get(ExecutionService)
+ exec_svc: ExecutionService = await scope.get(ExecutionService)
+ event_svc: EventService = await scope.get(EventService)
user_id = f"test_user_{uuid.uuid4().hex[:8]}"
- exec_result = await svc.execute_script(
+ exec_result = await exec_svc.execute_script(
script="print('filter test')",
user_id=user_id,
client_ip="127.0.0.1",
@@ -207,8 +209,8 @@ async def test_get_execution_events_with_filter(
lang_version="3.11",
)
- events = await svc.get_execution_events(
- exec_result.execution_id,
+ events = await event_svc.get_events_by_aggregate(
+ aggregate_id=exec_result.execution_id,
event_types=[EventType.EXECUTION_REQUESTED],
)
diff --git a/backend/tests/e2e/services/notifications/test_notification_service.py b/backend/tests/e2e/services/notifications/test_notification_service.py
index ffa63932..1a6ba243 100644
--- a/backend/tests/e2e/services/notifications/test_notification_service.py
+++ b/backend/tests/e2e/services/notifications/test_notification_service.py
@@ -2,7 +2,7 @@
import pytest
from app.db.repositories import NotificationRepository
-from app.domain.enums import NotificationChannel, NotificationSeverity
+from app.domain.enums import NotificationChannel, NotificationSeverity, NotificationStatus
from app.domain.notification import (
DomainNotificationListResult,
NotificationNotFoundError,
@@ -151,8 +151,12 @@ async def test_mark_as_read_success(self, scope: AsyncContainer) -> None:
)
# Mark as read
- result = await svc.mark_as_read(user_id, notification.notification_id)
- assert result is True
+ await svc.mark_as_read(user_id, notification.notification_id)
+
+ # Verify status changed to READ
+ result = await svc.list_notifications(user_id=user_id, status=NotificationStatus.READ)
+ read_ids = [n.notification_id for n in result.notifications]
+ assert notification.notification_id in read_ids
@pytest.mark.asyncio
async def test_mark_as_read_nonexistent_raises(
@@ -334,8 +338,12 @@ async def test_delete_notification_success(self, scope: AsyncContainer) -> None:
)
# Delete it
- result = await svc.delete_notification(user_id, notification.notification_id)
- assert result is True
+ await svc.delete_notification(user_id, notification.notification_id)
+
+ # Verify it's gone
+ result = await svc.list_notifications(user_id=user_id)
+ remaining_ids = [n.notification_id for n in result.notifications]
+ assert notification.notification_id not in remaining_ids
@pytest.mark.asyncio
async def test_delete_nonexistent_notification_raises(
@@ -530,12 +538,16 @@ async def test_full_notification_lifecycle(self, scope: AsyncContainer) -> None:
unread = await svc.get_unread_count(user_id)
assert unread >= 1
- # Mark as read
+ # Mark as read and verify unread count drops
await svc.mark_as_read(user_id, notification.notification_id)
-
- # Delete
- deleted = await svc.delete_notification(user_id, notification.notification_id)
- assert deleted is True
+ unread_after_read = await svc.get_unread_count(user_id)
+ assert unread_after_read < unread
+
+ # Delete and verify it's gone
+ await svc.delete_notification(user_id, notification.notification_id)
+ result = await svc.list_notifications(user_id=user_id)
+ remaining_ids = [n.notification_id for n in result.notifications]
+ assert notification.notification_id not in remaining_ids
@pytest.mark.asyncio
async def test_notification_with_subscription_filter(
diff --git a/backend/tests/e2e/services/saga/test_saga_service.py b/backend/tests/e2e/services/saga/test_saga_service.py
index e91bd844..e4bdd7f2 100644
--- a/backend/tests/e2e/services/saga/test_saga_service.py
+++ b/backend/tests/e2e/services/saga/test_saga_service.py
@@ -6,7 +6,7 @@
from app.domain.enums import SagaState, UserRole
from app.domain.execution import DomainExecutionCreate
from app.domain.saga import Saga, SagaAccessDeniedError, SagaListResult, SagaNotFoundError
-from app.schemas_pydantic.user import User
+from app.domain.user import User
from app.services.execution_service import ExecutionService
from app.services.saga.saga_service import SagaService
from dishka import AsyncContainer
@@ -26,6 +26,7 @@ def make_test_user(
role=role,
is_active=True,
is_superuser=role == UserRole.ADMIN,
+ hashed_password="unused",
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc),
)
diff --git a/backend/tests/e2e/test_auth_routes.py b/backend/tests/e2e/test_auth_routes.py
index 0f2b3a78..1308ace8 100644
--- a/backend/tests/e2e/test_auth_routes.py
+++ b/backend/tests/e2e/test_auth_routes.py
@@ -5,7 +5,6 @@
from app.schemas_pydantic.user import (
LoginResponse,
MessageResponse,
- TokenValidationResponse,
UserCreate,
UserResponse,
)
@@ -136,7 +135,7 @@ async def test_register_duplicate_email(
)
assert response.status_code == 409
- assert response.json()["detail"] == "Email already registered"
+ assert response.json()["detail"] == "User already exists"
@pytest.mark.asyncio
async def test_register_invalid_email_format(self, client: AsyncClient) -> None:
@@ -181,30 +180,6 @@ async def test_get_profile_unauthenticated(self, client: AsyncClient) -> None:
assert response.status_code == 401
-class TestAuthVerifyToken:
- """Tests for GET /api/v1/auth/verify-token."""
-
- @pytest.mark.asyncio
- async def test_verify_valid_token(self, test_user: AsyncClient) -> None:
- """Valid token returns TokenValidationResponse with valid=True."""
- response = await test_user.get("/api/v1/auth/verify-token")
-
- assert response.status_code == 200
- result = TokenValidationResponse.model_validate(response.json())
-
- assert result.valid is True
- assert result.username
- assert result.role in [UserRole.USER, UserRole.ADMIN]
- assert result.csrf_token
-
- @pytest.mark.asyncio
- async def test_verify_invalid_token(self, client: AsyncClient) -> None:
- """Invalid/missing token returns 401."""
- response = await client.get("/api/v1/auth/verify-token")
-
- assert response.status_code == 401
-
-
class TestAuthLogout:
"""Tests for POST /api/v1/auth/logout."""
diff --git a/backend/tests/e2e/test_dlq_routes.py b/backend/tests/e2e/test_dlq_routes.py
index ad7130eb..055ce3ab 100644
--- a/backend/tests/e2e/test_dlq_routes.py
+++ b/backend/tests/e2e/test_dlq_routes.py
@@ -7,7 +7,6 @@
DLQBatchRetryResponse,
DLQMessageDetail,
DLQMessagesResponse,
- DLQStats,
DLQTopicSummaryResponse,
)
from app.schemas_pydantic.user import MessageResponse
@@ -34,36 +33,13 @@ async def stored_dlq_message() -> DLQMessageDocument:
return doc
-class TestGetDLQStats:
- """Tests for GET /api/v1/dlq/stats."""
-
- @pytest.mark.asyncio
- async def test_get_dlq_stats(self, test_user: AsyncClient) -> None:
- """Get DLQ statistics."""
- response = await test_user.get("/api/v1/dlq/stats")
-
- assert response.status_code == 200
- stats = DLQStats.model_validate(response.json())
-
- assert stats.age_stats is not None
- assert stats.timestamp is not None
-
- @pytest.mark.asyncio
- async def test_get_dlq_stats_unauthenticated(
- self, client: AsyncClient
- ) -> None:
- """Unauthenticated request returns 401."""
- response = await client.get("/api/v1/dlq/stats")
- assert response.status_code == 401
-
-
class TestGetDLQMessages:
"""Tests for GET /api/v1/dlq/messages."""
@pytest.mark.asyncio
- async def test_get_dlq_messages(self, test_user: AsyncClient) -> None:
+ async def test_get_dlq_messages(self, test_admin: AsyncClient) -> None:
"""Get DLQ messages list."""
- response = await test_user.get("/api/v1/dlq/messages")
+ response = await test_admin.get("/api/v1/dlq/messages")
assert response.status_code == 200
result = DLQMessagesResponse.model_validate(response.json())
@@ -73,10 +49,10 @@ async def test_get_dlq_messages(self, test_user: AsyncClient) -> None:
@pytest.mark.asyncio
async def test_get_dlq_messages_with_pagination(
- self, test_user: AsyncClient
+ self, test_admin: AsyncClient
) -> None:
"""Pagination parameters work correctly."""
- response = await test_user.get(
+ response = await test_admin.get(
"/api/v1/dlq/messages",
params={"limit": 10, "offset": 0},
)
@@ -88,10 +64,10 @@ async def test_get_dlq_messages_with_pagination(
@pytest.mark.asyncio
async def test_get_dlq_messages_by_status(
- self, test_user: AsyncClient
+ self, test_admin: AsyncClient
) -> None:
"""Filter DLQ messages by status."""
- response = await test_user.get(
+ response = await test_admin.get(
"/api/v1/dlq/messages",
params={"status": DLQMessageStatus.PENDING},
)
@@ -104,10 +80,10 @@ async def test_get_dlq_messages_by_status(
@pytest.mark.asyncio
async def test_get_dlq_messages_by_topic(
- self, test_user: AsyncClient
+ self, test_admin: AsyncClient
) -> None:
"""Filter DLQ messages by topic."""
- response = await test_user.get(
+ response = await test_admin.get(
"/api/v1/dlq/messages",
params={"topic": "execution-events"},
)
@@ -117,10 +93,10 @@ async def test_get_dlq_messages_by_topic(
@pytest.mark.asyncio
async def test_get_dlq_messages_by_event_type(
- self, test_user: AsyncClient
+ self, test_admin: AsyncClient
) -> None:
"""Filter DLQ messages by event type."""
- response = await test_user.get(
+ response = await test_admin.get(
"/api/v1/dlq/messages",
params={"event_type": EventType.EXECUTION_REQUESTED},
)
@@ -134,22 +110,22 @@ class TestGetDLQMessage:
@pytest.mark.asyncio
async def test_get_dlq_message_not_found(
- self, test_user: AsyncClient
+ self, test_admin: AsyncClient
) -> None:
"""Get nonexistent DLQ message returns 404."""
- response = await test_user.get(
+ response = await test_admin.get(
"/api/v1/dlq/messages/nonexistent-event-id"
)
assert response.status_code == 404
@pytest.mark.asyncio
async def test_get_dlq_message_detail(
- self, test_user: AsyncClient, stored_dlq_message: DLQMessageDocument
+ self, test_admin: AsyncClient, stored_dlq_message: DLQMessageDocument
) -> None:
"""Get DLQ message detail by event_id."""
event_id = stored_dlq_message.event.event_id
- response = await test_user.get(
+ response = await test_admin.get(
f"/api/v1/dlq/messages/{event_id}"
)
assert response.status_code == 200
@@ -165,12 +141,12 @@ class TestRetryDLQMessages:
@pytest.mark.asyncio
async def test_retry_dlq_messages(
- self, test_user: AsyncClient, stored_dlq_message: DLQMessageDocument
+ self, test_admin: AsyncClient, stored_dlq_message: DLQMessageDocument
) -> None:
"""Retry a known DLQ message."""
event_ids = [stored_dlq_message.event.event_id]
- response = await test_user.post(
+ response = await test_admin.post(
"/api/v1/dlq/retry",
json={"event_ids": event_ids},
)
@@ -184,10 +160,10 @@ async def test_retry_dlq_messages(
@pytest.mark.asyncio
async def test_retry_dlq_messages_empty_list(
- self, test_user: AsyncClient
+ self, test_admin: AsyncClient
) -> None:
"""Retry with empty event IDs list."""
- response = await test_user.post(
+ response = await test_admin.post(
"/api/v1/dlq/retry",
json={"event_ids": []},
)
@@ -198,10 +174,10 @@ async def test_retry_dlq_messages_empty_list(
@pytest.mark.asyncio
async def test_retry_dlq_messages_nonexistent(
- self, test_user: AsyncClient
+ self, test_admin: AsyncClient
) -> None:
"""Retry nonexistent messages."""
- response = await test_user.post(
+ response = await test_admin.post(
"/api/v1/dlq/retry",
json={"event_ids": ["nonexistent-1", "nonexistent-2"]},
)
@@ -227,10 +203,10 @@ class TestSetRetryPolicy:
ids=lambda v: v if isinstance(v, str) else v.value,
)
async def test_set_retry_policy(
- self, test_user: AsyncClient, strategy: RetryStrategy, topic: str
+ self, test_admin: AsyncClient, strategy: RetryStrategy, topic: str
) -> None:
"""Set retry policy for each strategy type."""
- response = await test_user.post(
+ response = await test_admin.post(
"/api/v1/dlq/retry-policy",
json={
"topic": topic,
@@ -252,10 +228,10 @@ class TestDiscardDLQMessage:
@pytest.mark.asyncio
async def test_discard_dlq_message_not_found(
- self, test_user: AsyncClient
+ self, test_admin: AsyncClient
) -> None:
"""Discard nonexistent message returns 404."""
- response = await test_user.delete(
+ response = await test_admin.delete(
"/api/v1/dlq/messages/nonexistent-event-id",
params={"reason": "Test discard"},
)
@@ -263,12 +239,12 @@ async def test_discard_dlq_message_not_found(
@pytest.mark.asyncio
async def test_discard_dlq_message(
- self, test_user: AsyncClient, stored_dlq_message: DLQMessageDocument
+ self, test_admin: AsyncClient, stored_dlq_message: DLQMessageDocument
) -> None:
"""Discard a known DLQ message."""
event_id = stored_dlq_message.event.event_id
- response = await test_user.delete(
+ response = await test_admin.delete(
f"/api/v1/dlq/messages/{event_id}",
params={"reason": "Test discard for E2E testing"},
)
@@ -280,17 +256,17 @@ async def test_discard_dlq_message(
assert "discarded" in msg_result.message.lower()
# Verify message is actually gone or marked discarded
- get_resp = await test_user.get(f"/api/v1/dlq/messages/{event_id}")
+ get_resp = await test_admin.get(f"/api/v1/dlq/messages/{event_id}")
if get_resp.status_code == 200:
detail = DLQMessageDetail.model_validate(get_resp.json())
assert detail.status == DLQMessageStatus.DISCARDED
@pytest.mark.asyncio
async def test_discard_dlq_message_requires_reason(
- self, test_user: AsyncClient
+ self, test_admin: AsyncClient
) -> None:
"""Discard requires reason parameter."""
- response = await test_user.delete(
+ response = await test_admin.delete(
"/api/v1/dlq/messages/some-event-id"
)
assert response.status_code == 422
@@ -300,9 +276,9 @@ class TestGetDLQTopics:
"""Tests for GET /api/v1/dlq/topics."""
@pytest.mark.asyncio
- async def test_get_dlq_topics(self, test_user: AsyncClient) -> None:
+ async def test_get_dlq_topics(self, test_admin: AsyncClient) -> None:
"""Get DLQ topics summary."""
- response = await test_user.get("/api/v1/dlq/topics")
+ response = await test_admin.get("/api/v1/dlq/topics")
assert response.status_code == 200
topics = [
diff --git a/backend/tests/e2e/test_events_routes.py b/backend/tests/e2e/test_events_routes.py
index 7c774a0b..cb9be1f2 100644
--- a/backend/tests/e2e/test_events_routes.py
+++ b/backend/tests/e2e/test_events_routes.py
@@ -5,7 +5,6 @@
DeleteEventResponse,
EventListResponse,
EventStatistics,
- PublishEventRequest,
PublishEventResponse,
ReplayAggregateResponse,
)
@@ -308,60 +307,6 @@ async def test_publish_event_forbidden_for_user(
assert response.status_code == 403
-class TestAggregateEvents:
- """Tests for POST /api/v1/events/aggregate."""
-
- @pytest.mark.asyncio
- async def test_aggregate_events(
- self, test_user: AsyncClient, created_execution: ExecutionResponse
- ) -> None:
- """Aggregate events with MongoDB pipeline."""
- response = await test_user.post(
- "/api/v1/events/aggregate",
- json={
- "pipeline": [
- {"$group": {"_id": "$event_type", "count": {"$sum": 1}}}
- ],
- "limit": 100,
- },
- )
-
- assert response.status_code == 200
- result = response.json()
- assert isinstance(result, list)
- assert len(result) >= 1
- for item in result:
- assert "_id" in item and "count" in item and item["count"] > 0
-
-
-class TestListEventTypes:
- """Tests for GET /api/v1/events/types/list."""
-
- @pytest.mark.asyncio
- async def test_list_event_types(self, test_admin: AsyncClient) -> None:
- """List available event types."""
- # First create an event so there's at least one type (requires admin)
- request = PublishEventRequest(
- event_type=EventType.SCRIPT_SAVED,
- payload={
- "script_id": "test-script",
- "user_id": "test-user",
- "title": "Test Script",
- "language": "python",
- },
- )
- await test_admin.post("/api/v1/events/publish", json=request.model_dump())
-
- # Query with admin (admins can see all events, users only see their own)
- response = await test_admin.get("/api/v1/events/types/list")
-
- assert response.status_code == 200
- result = response.json()
- assert isinstance(result, list)
- assert len(result) > 0
- assert EventType.SCRIPT_SAVED in result
-
-
class TestDeleteEvent:
"""Tests for DELETE /api/v1/events/{event_id} (admin only)."""
diff --git a/backend/tests/e2e/test_grafana_alerts_routes.py b/backend/tests/e2e/test_grafana_alerts_routes.py
deleted file mode 100644
index 86adbfb2..00000000
--- a/backend/tests/e2e/test_grafana_alerts_routes.py
+++ /dev/null
@@ -1,234 +0,0 @@
-import pytest
-from app.schemas_pydantic.grafana import AlertResponse
-from httpx import AsyncClient
-
-pytestmark = [pytest.mark.e2e]
-
-
-class TestGrafanaWebhook:
- """Tests for POST /api/v1/alerts/grafana."""
-
- @pytest.mark.asyncio
- async def test_receive_grafana_alert(self, client: AsyncClient) -> None:
- """Receive a Grafana alert webhook."""
- response = await client.post(
- "/api/v1/alerts/grafana",
- json={
- "status": "firing",
- "receiver": "integr8s-receiver",
- "alerts": [
- {
- "status": "firing",
- "labels": {
- "alertname": "HighCPUUsage",
- "severity": "critical",
- "instance": "worker-1",
- },
- "annotations": {
- "summary": "High CPU usage detected",
- "description": "CPU usage is above 90%",
- },
- "valueString": "95%",
- }
- ],
- "groupLabels": {
- "alertname": "HighCPUUsage",
- },
- "commonLabels": {
- "severity": "critical",
- },
- "commonAnnotations": {
- "summary": "High CPU usage detected",
- },
- },
- )
-
- assert response.status_code == 200
- result = AlertResponse.model_validate(response.json())
-
- assert result.message
- assert result.alerts_received == 1
- assert result.alerts_processed == 1
- assert len(result.errors) == 0
-
- @pytest.mark.asyncio
- async def test_receive_multiple_grafana_alerts(
- self, client: AsyncClient
- ) -> None:
- """Receive multiple alerts in one webhook."""
- response = await client.post(
- "/api/v1/alerts/grafana",
- json={
- "status": "firing",
- "receiver": "integr8s-receiver",
- "alerts": [
- {
- "status": "firing",
- "labels": {"alertname": "Alert1", "severity": "warning"},
- "annotations": {"summary": "Alert 1"},
- },
- {
- "status": "firing",
- "labels": {"alertname": "Alert2", "severity": "critical"},
- "annotations": {"summary": "Alert 2"},
- },
- {
- "status": "resolved",
- "labels": {"alertname": "Alert3", "severity": "info"},
- "annotations": {"summary": "Alert 3"},
- },
- ],
- "groupLabels": {},
- "commonLabels": {},
- "commonAnnotations": {},
- },
- )
-
- assert response.status_code == 200
- result = AlertResponse.model_validate(response.json())
-
- assert result.alerts_received == 3
- assert result.alerts_processed == 3
-
- @pytest.mark.asyncio
- async def test_receive_grafana_alert_resolved(
- self, client: AsyncClient
- ) -> None:
- """Receive a resolved alert."""
- response = await client.post(
- "/api/v1/alerts/grafana",
- json={
- "status": "resolved",
- "receiver": "integr8s-receiver",
- "alerts": [
- {
- "status": "resolved",
- "labels": {
- "alertname": "HighMemoryUsage",
- "severity": "warning",
- },
- "annotations": {
- "summary": "Memory usage back to normal",
- },
- }
- ],
- "groupLabels": {},
- "commonLabels": {},
- "commonAnnotations": {},
- },
- )
-
- assert response.status_code == 200
- result = AlertResponse.model_validate(response.json())
- assert result.alerts_received == 1
-
- @pytest.mark.asyncio
- async def test_receive_grafana_alert_empty_alerts(
- self, client: AsyncClient
- ) -> None:
- """Receive webhook with empty alerts list."""
- response = await client.post(
- "/api/v1/alerts/grafana",
- json={
- "status": "firing",
- "receiver": "integr8s-receiver",
- "alerts": [],
- "groupLabels": {},
- "commonLabels": {},
- "commonAnnotations": {},
- },
- )
-
- assert response.status_code == 200
- result = AlertResponse.model_validate(response.json())
- assert result.alerts_received == 0
- assert result.alerts_processed == 0
-
- @pytest.mark.asyncio
- async def test_receive_grafana_alert_minimal_payload(
- self, client: AsyncClient
- ) -> None:
- """Receive webhook with minimal payload."""
- response = await client.post(
- "/api/v1/alerts/grafana",
- json={
- "alerts": [
- {
- "labels": {"alertname": "MinimalAlert"},
- }
- ],
- },
- )
-
- assert response.status_code == 200
- result = AlertResponse.model_validate(response.json())
- assert result.alerts_received == 1
-
- @pytest.mark.asyncio
- async def test_receive_grafana_alert_with_value_string(
- self, client: AsyncClient
- ) -> None:
- """Receive alert with valueString field."""
- response = await client.post(
- "/api/v1/alerts/grafana",
- json={
- "status": "firing",
- "alerts": [
- {
- "status": "firing",
- "labels": {
- "alertname": "DiskSpaceLow",
- "instance": "server-1",
- },
- "annotations": {
- "summary": "Disk space is running low",
- },
- "valueString": "10% available",
- }
- ],
- },
- )
-
- assert response.status_code == 200
- result = AlertResponse.model_validate(response.json())
- assert result.alerts_received == 1
-
-
-class TestGrafanaTestEndpoint:
- """Tests for GET /api/v1/alerts/grafana/test."""
-
- @pytest.mark.asyncio
- async def test_grafana_test_endpoint(self, client: AsyncClient) -> None:
- """Test the Grafana webhook test endpoint."""
- response = await client.get("/api/v1/alerts/grafana/test")
-
- assert response.status_code == 200
- result = response.json()
-
- assert result["status"] == "ok"
- assert "message" in result
- assert "webhook_url" in result
- assert result["webhook_url"] == "/api/v1/alerts/grafana"
-
- @pytest.mark.asyncio
- async def test_grafana_test_endpoint_as_user(
- self, test_user: AsyncClient
- ) -> None:
- """Authenticated user can access test endpoint."""
- response = await test_user.get("/api/v1/alerts/grafana/test")
-
- assert response.status_code == 200
- result = response.json()
- assert result["status"] == "ok"
-
- @pytest.mark.asyncio
- async def test_grafana_test_endpoint_as_admin(
- self, test_admin: AsyncClient
- ) -> None:
- """Admin can access test endpoint."""
- response = await test_admin.get("/api/v1/alerts/grafana/test")
-
- assert response.status_code == 200
- result = response.json()
- assert result["status"] == "ok"
- assert "Grafana webhook endpoint is ready" in result["message"]
diff --git a/backend/tests/e2e/test_health_routes.py b/backend/tests/e2e/test_health_routes.py
index f90c6166..1d3e20e3 100644
--- a/backend/tests/e2e/test_health_routes.py
+++ b/backend/tests/e2e/test_health_routes.py
@@ -2,7 +2,7 @@
import time
import pytest
-from app.api.routes.health import LivenessResponse, ReadinessResponse
+from app.api.routes.health import LivenessResponse
from httpx import AsyncClient
pytestmark = [pytest.mark.e2e]
@@ -22,17 +22,6 @@ async def test_liveness_probe(self, client: AsyncClient) -> None:
assert result.status == "ok"
assert result.uptime_seconds >= 0
- @pytest.mark.asyncio
- async def test_readiness_probe(self, client: AsyncClient) -> None:
- """GET /health/ready returns 200 with status ok."""
- response = await client.get("/api/v1/health/ready")
-
- assert response.status_code == 200
- result = ReadinessResponse.model_validate(response.json())
-
- assert result.status == "ok"
- assert result.uptime_seconds >= 0
-
@pytest.mark.asyncio
async def test_liveness_no_auth_required(self, client: AsyncClient) -> None:
"""Liveness probe does not require authentication."""
@@ -40,12 +29,6 @@ async def test_liveness_no_auth_required(self, client: AsyncClient) -> None:
response = await client.get("/api/v1/health/live")
assert response.status_code == 200
- @pytest.mark.asyncio
- async def test_readiness_no_auth_required(self, client: AsyncClient) -> None:
- """Readiness probe does not require authentication."""
- response = await client.get("/api/v1/health/ready")
- assert response.status_code == 200
-
@pytest.mark.asyncio
async def test_uptime_increases(self, client: AsyncClient) -> None:
"""Uptime should be consistent between calls."""
@@ -79,6 +62,7 @@ async def test_nonexistent_health_routes_return_404(self, client: AsyncClient) -
for path in [
"/api/v1/health/healthz",
"/api/v1/health/health",
+ "/api/v1/health/ready",
"/api/v1/health/readyz",
]:
r = await client.get(path)
diff --git a/backend/tests/e2e/test_replay_routes.py b/backend/tests/e2e/test_replay_routes.py
index 61456b2d..59b27566 100644
--- a/backend/tests/e2e/test_replay_routes.py
+++ b/backend/tests/e2e/test_replay_routes.py
@@ -309,8 +309,8 @@ async def test_list_replay_sessions(
for session in sessions:
assert session.session_id is not None
- assert session.replay_type in list(ReplayType)
- assert session.target in list(ReplayTarget)
+ assert session.config.replay_type in list(ReplayType)
+ assert session.config.target in list(ReplayTarget)
assert session.status in list(ReplayStatus)
assert session.total_events >= 0
assert session.replayed_events >= 0
diff --git a/backend/tests/load/strategies.py b/backend/tests/load/strategies.py
index da6af712..44c2cd97 100644
--- a/backend/tests/load/strategies.py
+++ b/backend/tests/load/strategies.py
@@ -1,7 +1,5 @@
from __future__ import annotations
-from datetime import datetime, timedelta
-
from hypothesis import strategies as st
# Type alias for JSON values
@@ -43,43 +41,3 @@
"password": password,
}
)
-
-
-# Grafana webhook strategy (approximate schema)
-severity = st.sampled_from(["info", "warning", "error", "critical"]) # common values
-label_key = st.text(min_size=1, max_size=24)
-label_val = st.text(min_size=0, max_size=64)
-label_dict = st.dictionaries(label_key, label_val, max_size=8)
-annotation_dict = st.dictionaries(label_key, label_val, max_size=8)
-
-def _iso_time() -> st.SearchStrategy[str]:
- base = datetime(2024, 1, 1)
- return st.integers(min_value=0, max_value=86_400).map(
- lambda sec: (base + timedelta(seconds=int(sec))).isoformat() + "Z"
- )
-
-alert = st.fixed_dictionaries(
- {
- "status": st.sampled_from(["firing", "resolved"]),
- "labels": label_dict,
- "annotations": annotation_dict,
- "startsAt": _iso_time(),
- "endsAt": _iso_time(),
- "generatorURL": st.text(min_size=0, max_size=120),
- "fingerprint": st.text(min_size=1, max_size=64),
- }
-)
-
-grafana_webhook = st.fixed_dictionaries(
- {
- "receiver": st.text(min_size=1, max_size=64),
- "status": st.sampled_from(["firing", "resolved"]),
- "alerts": st.lists(alert, min_size=1, max_size=5),
- "groupKey": st.text(min_size=0, max_size=64),
- "groupLabels": label_dict,
- "commonLabels": label_dict,
- "commonAnnotations": annotation_dict,
- "externalURL": st.text(min_size=0, max_size=120),
- "version": st.text(min_size=1, max_size=16),
- }
-)
diff --git a/backend/tests/unit/core/metrics/test_execution_and_events_metrics.py b/backend/tests/unit/core/metrics/test_execution_and_events_metrics.py
index 49e4f7d3..a102a02f 100644
--- a/backend/tests/unit/core/metrics/test_execution_and_events_metrics.py
+++ b/backend/tests/unit/core/metrics/test_execution_and_events_metrics.py
@@ -20,7 +20,7 @@ def test_execution_metrics_methods(test_settings: Settings) -> None:
m.record_queue_wait_time(0.1, "python-3.11")
m.record_execution_assigned()
m.record_execution_queued()
- m.record_execution_scheduled("ok")
+ m.record_execution_scheduled()
m.update_cpu_available(100.0)
m.update_memory_available(512.0)
m.update_gpu_available(1)
diff --git a/docs/operations/grafana-integration.md b/docs/operations/grafana-integration.md
index 93c0637f..19663179 100644
--- a/docs/operations/grafana-integration.md
+++ b/docs/operations/grafana-integration.md
@@ -1,120 +1,122 @@
# Grafana Integration
-The platform accepts Grafana alert webhooks and converts them into in-app notifications. This allows operators to
-receive Grafana alerts directly in the application UI without leaving the platform.
+Grafana connects to Victoria Metrics to visualize platform metrics and to Jaeger for trace exploration. Alerting uses
+Grafana's built-in unified alerting engine with provisioned contact points and alert rules — no custom backend endpoints
+involved.
-## Webhook Endpoint
+## Dashboards
-Configure Grafana to send webhooks to `POST /api/v1/alerts/grafana`. A test endpoint is available to verify connectivity.
+Grafana is available at `http://localhost:3000` when the stack is running (anonymous viewer access enabled by default).
+Victoria Metrics serves as the Prometheus-compatible data source. See [Metrics Reference](metrics-reference.md) for the
+full metric catalog and example PromQL queries.
-
+## Alerting Architecture
-## Webhook Payload
-
-The endpoint expects Grafana's standard webhook format:
-
-```python
---8<-- "backend/app/schemas_pydantic/grafana.py:8:22"
+```mermaid
+flowchart LR
+ VM["Victoria Metrics"] --> Grafana
+ Grafana -->|"evaluate rules"| Grafana
+ Grafana -->|"notify"| Slack["Slack"]
+ Grafana -->|"notify"| Email["Email"]
+ Grafana -->|"notify"| PagerDuty["PagerDuty / etc."]
```
-Example payload:
-
-```json
-{
- "status": "firing",
- "receiver": "integr8scode",
- "alerts": [
- {
- "status": "firing",
- "labels": {
- "alertname": "HighMemoryUsage",
- "severity": "warning",
- "instance": "backend:8000"
- },
- "annotations": {
- "summary": "Memory usage above 80%",
- "description": "Backend instance memory usage is 85%"
- }
- }
- ],
- "commonLabels": {
- "env": "production"
- }
-}
-```
-
-## Severity Mapping
-
-Grafana severity labels are mapped to notification severity levels:
+Grafana's unified alerting engine evaluates rules on a schedule, queries Victoria Metrics, and sends notifications
+directly to configured contact points (Slack, email, PagerDuty, OpsGenie, webhooks, etc.). This is the standard
+Grafana approach — no intermediate backend service is needed.
-```python
---8<-- "backend/app/services/grafana_alert_processor.py:14:19"
-```
-
-| Grafana Severity | Notification Severity |
-|------------------|-----------------------|
-| `critical` | HIGH |
-| `error` | HIGH |
-| `warning` | MEDIUM |
-| `info` | LOW |
+## Provisioning
-Resolved alerts (status `ok` or `resolved`) are always mapped to LOW severity regardless of the original severity label.
+Alert configuration is managed via YAML files in `backend/grafana/provisioning/alerting/`. Grafana loads these on
+startup, so alert rules, contact points, and notification policies are version-controlled and reproducible.
-## Processing Flow
+The provisioning file ships with commented-out examples for common setups:
-The `GrafanaAlertProcessor` processes each alert in the webhook:
+### Contact Points
-1. Extract severity from alert labels or common labels
-2. Map severity to notification level
-3. Extract title from `alertname` label or `title` annotation
-4. Build message from `summary` and `description` annotations
-5. Create system notification with metadata
+Contact points define where notifications go. Uncomment and configure in `alerting.yml`:
-```python
---8<-- "backend/app/services/grafana_alert_processor.py:73:102"
+```yaml
+contactPoints:
+ - orgId: 1
+ name: slack-notifications
+ receivers:
+ - uid: slack-receiver
+ type: slack
+ settings:
+ url: https://hooks.slack.com/services/YOUR/WEBHOOK/URL
+ recipient: "#alerts"
```
-## Notification Content
-
-The processor builds notification content as follows:
+Grafana supports 20+ contact point types out of the box: Slack, email, PagerDuty, OpsGenie, Microsoft Teams, generic
+webhooks, and more. See the
+[Grafana contact points documentation](https://grafana.com/docs/grafana/latest/alerting/configure-notifications/manage-contact-points/)
+for the full list.
-- **Title**: `labels.alertname` or `annotations.title` or "Grafana Alert"
-- **Message**: `annotations.summary` and `annotations.description` joined by newlines
-- **Tags**: `["external_alert", "grafana", "entity:external_alert"]`
-- **Metadata**: Alert labels, common labels, and status
+### Notification Policies
-## Response Format
+Policies route alerts to the right contact point based on labels:
-The endpoint returns processing status:
+```yaml
+policies:
+ - orgId: 1
+ receiver: slack-notifications
+ group_by: ["alertname", "namespace"]
+ group_wait: 30s
+ group_interval: 5m
+ repeat_interval: 4h
+```
-```json
-{
- "message": "Webhook received and processed",
- "alerts_received": 3,
- "alerts_processed": 3,
- "errors": []
-}
+### Alert Rules
+
+Rules query Victoria Metrics and fire when thresholds are breached. Example for HTTP 5xx error rate:
+
+```yaml
+groups:
+ - orgId: 1
+ name: backend-alerts
+ folder: Integr8sCode
+ interval: 1m
+ rules:
+ - uid: high-error-rate
+ title: High HTTP 5xx Error Rate
+ condition: C
+ data:
+ - refId: A
+ relativeTimeRange:
+ from: 300
+ to: 0
+ datasourceUid: victoria-metrics
+ model:
+ expr: >
+ sum(rate(http_requests_total{status=~"5.."}[5m]))
+ / sum(rate(http_requests_total[5m])) * 100
+ for: 5m
+ labels:
+ severity: warning
+ annotations:
+ summary: "HTTP 5xx error rate is above 5%"
```
-If any alerts fail to process, the error messages are included in the `errors` array but the endpoint still returns 200
-for successfully processed alerts.
+## Configuration
-## Grafana Configuration
+Unified alerting is enabled in `backend/grafana/grafana.ini`:
-To configure Grafana to send alerts:
+```ini
+[unified_alerting]
+enabled = true
-1. Navigate to **Alerting > Contact points**
-2. Create a new contact point with type **Webhook**
-3. Set URL to `https://your-domain/api/v1/alerts/grafana`
-4. For authenticated environments, configure appropriate headers
+[alerting]
+enabled = false
+```
-The webhook URL should be accessible from your Grafana instance. If using network policies, ensure Grafana can reach the
-backend service.
+The `[alerting]` section controls the legacy alerting engine (Grafana < 9) and stays disabled. `[unified_alerting]`
+is the modern engine used for all provisioned rules.
## Key Files
-| File | Purpose |
-|----------------------------------------------------------------------------------------------------------------------------------------------|-------------------------|
-| [`services/grafana_alert_processor.py`](https://github.com/HardMax71/Integr8sCode/blob/main/backend/app/services/grafana_alert_processor.py) | Alert processing logic |
-| [`api/routes/grafana_alerts.py`](https://github.com/HardMax71/Integr8sCode/blob/main/backend/app/api/routes/grafana_alerts.py) | Webhook endpoint |
-| [`schemas_pydantic/grafana.py`](https://github.com/HardMax71/Integr8sCode/blob/main/backend/app/schemas_pydantic/grafana.py) | Request/response models |
+| File | Purpose |
+|----------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------|
+| [`grafana/grafana.ini`](https://github.com/HardMax71/Integr8sCode/blob/main/backend/grafana/grafana.ini) | Grafana server configuration |
+| [`grafana/provisioning/alerting/alerting.yml`](https://github.com/HardMax71/Integr8sCode/blob/main/backend/grafana/provisioning/alerting/alerting.yml) | Alert rules and contact points |
+| [`grafana/provisioning/datasources/`](https://github.com/HardMax71/Integr8sCode/tree/main/backend/grafana/provisioning/datasources) | Victoria Metrics data source |
diff --git a/docs/operations/notification-types.md b/docs/operations/notification-types.md
index 679f861e..7c308b4e 100644
--- a/docs/operations/notification-types.md
+++ b/docs/operations/notification-types.md
@@ -36,7 +36,7 @@ Producers include structured tags for filtering, UI actions, and correlation.
| Tag type | Purpose | Examples |
|-----------|--------------------------------|----------------------------------|
-| Category | What the notification is about | `execution`, `external_alert` |
+| Category | What the notification is about | `execution` |
| Entity | Entity type | `entity:execution` |
| Reference | Link to specific resource | `exec:` |
| Outcome | What happened | `completed`, `failed`, `timeout` |
@@ -55,12 +55,6 @@ Execution failed:
["execution", "failed", "entity:execution", "exec:2c1b...e8"]
```
-Grafana alert:
-
-```json
-["external_alert", "grafana", "entity:external_alert"]
-```
-
## Throttling
The service throttles notifications per user per severity window:
diff --git a/docs/reference/openapi.json b/docs/reference/openapi.json
index 5732caa4..2409a9e0 100644
--- a/docs/reference/openapi.json
+++ b/docs/reference/openapi.json
@@ -97,7 +97,7 @@
}
},
"409": {
- "description": "Email already registered",
+ "description": "User already exists",
"content": {
"application/json": {
"schema": {
@@ -141,38 +141,6 @@
}
}
},
- "/api/v1/auth/verify-token": {
- "get": {
- "tags": [
- "authentication"
- ],
- "summary": "Verify Token",
- "description": "Verify the current access token.",
- "operationId": "verify_token_api_v1_auth_verify_token_get",
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/TokenValidationResponse"
- }
- }
- }
- },
- "401": {
- "description": "Missing or invalid access token",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/ErrorResponse"
- }
- }
- }
- }
- }
- }
- },
"/api/v1/auth/logout": {
"post": {
"tags": [
@@ -1622,50 +1590,6 @@
}
}
},
- "/api/v1/health/ready": {
- "get": {
- "tags": [
- "Health"
- ],
- "summary": "Readiness",
- "description": "Simple readiness probe. Extend with dependency checks if needed.",
- "operationId": "readiness_api_v1_health_ready_get",
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/ReadinessResponse"
- }
- }
- }
- }
- }
- }
- },
- "/api/v1/dlq/stats": {
- "get": {
- "tags": [
- "Dead Letter Queue"
- ],
- "summary": "Get Dlq Statistics",
- "description": "Get summary statistics for the dead letter queue.",
- "operationId": "get_dlq_statistics_api_v1_dlq_stats_get",
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/DLQStats"
- }
- }
- }
- }
- }
- }
- },
"/api/v1/dlq/messages": {
"get": {
"tags": [
@@ -2934,78 +2858,6 @@
}
}
},
- "/api/v1/events/aggregate": {
- "post": {
- "tags": [
- "events"
- ],
- "summary": "Aggregate Events",
- "description": "Run a custom aggregation pipeline on the event store.",
- "operationId": "aggregate_events_api_v1_events_aggregate_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/EventAggregationRequest"
- }
- }
- },
- "required": true
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "type": "object"
- },
- "type": "array",
- "title": "Response Aggregate Events Api V1 Events Aggregate Post"
- }
- }
- }
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- }
- }
- }
- }
- },
- "/api/v1/events/types/list": {
- "get": {
- "tags": [
- "events"
- ],
- "summary": "List Event Types",
- "description": "List all distinct event types in the store.",
- "operationId": "list_event_types_api_v1_events_types_list_get",
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "type": "string"
- },
- "type": "array",
- "title": "Response List Event Types Api V1 Events Types List Get"
- }
- }
- }
- }
- }
- }
- },
"/api/v1/events/replay/{aggregate_id}": {
"post": {
"tags": [
@@ -5047,7 +4899,7 @@
"sagas"
],
"summary": "Get Saga Status",
- "description": "Get saga status by ID.\n\nArgs:\n saga_id: The saga identifier\n request: FastAPI request object\n saga_service: Saga service from DI\n auth_service: Auth service from DI\n\nReturns:\n Saga status response\n\nRaises:\n HTTPException: 404 if saga not found, 403 if access denied",
+ "description": "Get saga status by ID.",
"operationId": "get_saga_status_api_v1_sagas__saga_id__get",
"parameters": [
{
@@ -5110,7 +4962,7 @@
"sagas"
],
"summary": "Get Execution Sagas",
- "description": "Get all sagas for an execution.\n\nArgs:\n execution_id: The execution identifier\n request: FastAPI request object\n saga_service: Saga service from DI\n auth_service: Auth service from DI\n state: Optional state filter\n limit: Maximum number of results\n skip: Number of results to skip\n\nReturns:\n Paginated list of sagas for the execution\n\nRaises:\n HTTPException: 403 if access denied",
+ "description": "Get all sagas for an execution.",
"operationId": "get_execution_sagas_api_v1_sagas_execution__execution_id__get",
"parameters": [
{
@@ -5204,7 +5056,7 @@
"sagas"
],
"summary": "List Sagas",
- "description": "List sagas accessible by the current user.\n\nArgs:\n request: FastAPI request object\n saga_service: Saga service from DI\n auth_service: Auth service from DI\n state: Optional state filter\n limit: Maximum number of results\n skip: Number of results to skip\n\nReturns:\n Paginated list of sagas",
+ "description": "List sagas accessible by the current user.",
"operationId": "list_sagas_api_v1_sagas__get",
"parameters": [
{
@@ -5279,7 +5131,7 @@
"sagas"
],
"summary": "Cancel Saga",
- "description": "Cancel a running saga.\n\nArgs:\n saga_id: The saga identifier\n request: FastAPI request object\n saga_service: Saga service from DI\n auth_service: Auth service from DI\n\nReturns:\n Cancellation response with success status\n\nRaises:\n HTTPException: 404 if not found, 403 if denied, 400 if invalid state",
+ "description": "Cancel a running saga.",
"operationId": "cancel_saga_api_v1_sagas__saga_id__cancel_post",
"parameters": [
{
@@ -5345,74 +5197,6 @@
}
}
}
- },
- "/api/v1/alerts/grafana": {
- "post": {
- "tags": [
- "alerts"
- ],
- "summary": "Receive Grafana Alerts",
- "description": "Receive and process a Grafana alerting webhook payload.",
- "operationId": "receive_grafana_alerts_api_v1_alerts_grafana_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/GrafanaWebhook"
- }
- }
- },
- "required": true
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/AlertResponse"
- }
- }
- }
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- }
- }
- }
- }
- },
- "/api/v1/alerts/grafana/test": {
- "get": {
- "tags": [
- "alerts"
- ],
- "summary": "Test Grafana Alert Endpoint",
- "description": "Verify the Grafana webhook endpoint is reachable.",
- "operationId": "test_grafana_alert_endpoint_api_v1_alerts_grafana_test_get",
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "additionalProperties": {
- "type": "string"
- },
- "type": "object",
- "title": "Response Test Grafana Alert Endpoint Api V1 Alerts Grafana Test Get"
- }
- }
- }
- }
- }
- }
}
},
"components": {
@@ -5683,58 +5467,6 @@
],
"title": "AdminUserOverview"
},
- "AgeStatistics": {
- "properties": {
- "min_age_seconds": {
- "type": "number",
- "title": "Min Age Seconds",
- "default": 0.0
- },
- "max_age_seconds": {
- "type": "number",
- "title": "Max Age Seconds",
- "default": 0.0
- },
- "avg_age_seconds": {
- "type": "number",
- "title": "Avg Age Seconds",
- "default": 0.0
- }
- },
- "type": "object",
- "title": "AgeStatistics",
- "description": "Age statistics for DLQ messages."
- },
- "AlertResponse": {
- "properties": {
- "message": {
- "type": "string",
- "title": "Message"
- },
- "alerts_received": {
- "type": "integer",
- "title": "Alerts Received"
- },
- "alerts_processed": {
- "type": "integer",
- "title": "Alerts Processed"
- },
- "errors": {
- "items": {
- "type": "string"
- },
- "type": "array",
- "title": "Errors"
- }
- },
- "type": "object",
- "required": [
- "message",
- "alerts_received",
- "alerts_processed"
- ],
- "title": "AlertResponse"
- },
"AllocateResourcesCommandEvent": {
"properties": {
"event_id": {
@@ -5935,16 +5667,10 @@
"CancelExecutionRequest": {
"properties": {
"reason": {
- "anyOf": [
- {
- "type": "string"
- },
- {
- "type": "null"
- }
- ],
+ "type": "string",
"title": "Reason",
- "description": "Reason for cancellation"
+ "description": "Reason for cancellation",
+ "default": "User requested cancellation"
}
},
"type": "object",
@@ -7175,49 +6901,6 @@
],
"title": "DLQRetryResult"
},
- "DLQStats": {
- "properties": {
- "by_status": {
- "additionalProperties": {
- "type": "integer"
- },
- "type": "object",
- "title": "By Status"
- },
- "by_topic": {
- "items": {
- "$ref": "#/components/schemas/TopicStatistic"
- },
- "type": "array",
- "title": "By Topic"
- },
- "by_event_type": {
- "items": {
- "$ref": "#/components/schemas/EventTypeStatistic"
- },
- "type": "array",
- "title": "By Event Type"
- },
- "age_stats": {
- "$ref": "#/components/schemas/AgeStatistics"
- },
- "timestamp": {
- "type": "string",
- "format": "date-time",
- "title": "Timestamp"
- }
- },
- "type": "object",
- "required": [
- "by_status",
- "by_topic",
- "by_event_type",
- "age_stats",
- "timestamp"
- ],
- "title": "DLQStats",
- "description": "Statistics for the Dead Letter Queue."
- },
"DLQTopicSummaryResponse": {
"properties": {
"topic": {
@@ -7594,31 +7277,6 @@
],
"title": "ErrorResponse"
},
- "EventAggregationRequest": {
- "properties": {
- "pipeline": {
- "items": {
- "type": "object"
- },
- "type": "array",
- "title": "Pipeline",
- "description": "MongoDB aggregation pipeline"
- },
- "limit": {
- "type": "integer",
- "maximum": 1000.0,
- "minimum": 1.0,
- "title": "Limit",
- "default": 100
- }
- },
- "type": "object",
- "required": [
- "pipeline"
- ],
- "title": "EventAggregationRequest",
- "description": "Request model for event aggregation queries."
- },
"EventBrowseRequest": {
"properties": {
"filters": {
@@ -9287,25 +8945,6 @@
"title": "EventTypeCountSchema",
"description": "Event count by type."
},
- "EventTypeStatistic": {
- "properties": {
- "event_type": {
- "type": "string",
- "title": "Event Type"
- },
- "count": {
- "type": "integer",
- "title": "Count"
- }
- },
- "type": "object",
- "required": [
- "event_type",
- "count"
- ],
- "title": "EventTypeStatistic",
- "description": "Statistics for a single event type."
- },
"ExampleScripts": {
"properties": {
"scripts": {
@@ -10273,104 +9912,6 @@
],
"title": "ExecutionTimeoutEvent"
},
- "GrafanaAlertItem": {
- "properties": {
- "status": {
- "anyOf": [
- {
- "type": "string"
- },
- {
- "type": "null"
- }
- ],
- "title": "Status"
- },
- "labels": {
- "additionalProperties": {
- "type": "string"
- },
- "type": "object",
- "title": "Labels"
- },
- "annotations": {
- "additionalProperties": {
- "type": "string"
- },
- "type": "object",
- "title": "Annotations"
- },
- "valueString": {
- "anyOf": [
- {
- "type": "string"
- },
- {
- "type": "null"
- }
- ],
- "title": "Valuestring"
- }
- },
- "type": "object",
- "title": "GrafanaAlertItem"
- },
- "GrafanaWebhook": {
- "properties": {
- "status": {
- "anyOf": [
- {
- "type": "string"
- },
- {
- "type": "null"
- }
- ],
- "title": "Status"
- },
- "receiver": {
- "anyOf": [
- {
- "type": "string"
- },
- {
- "type": "null"
- }
- ],
- "title": "Receiver"
- },
- "alerts": {
- "items": {
- "$ref": "#/components/schemas/GrafanaAlertItem"
- },
- "type": "array",
- "title": "Alerts"
- },
- "groupLabels": {
- "additionalProperties": {
- "type": "string"
- },
- "type": "object",
- "title": "Grouplabels"
- },
- "commonLabels": {
- "additionalProperties": {
- "type": "string"
- },
- "type": "object",
- "title": "Commonlabels"
- },
- "commonAnnotations": {
- "additionalProperties": {
- "type": "string"
- },
- "type": "object",
- "title": "Commonannotations"
- }
- },
- "type": "object",
- "title": "GrafanaWebhook"
- },
"HTTPValidationError": {
"properties": {
"detail": {
@@ -10506,8 +10047,7 @@
"title": "Username"
},
"role": {
- "type": "string",
- "title": "Role"
+ "$ref": "#/components/schemas/UserRole"
},
"csrf_token": {
"type": "string",
@@ -12416,27 +11956,6 @@
"title": "RateLimitUpdateResponse",
"description": "Response model for rate limit update."
},
- "ReadinessResponse": {
- "properties": {
- "status": {
- "type": "string",
- "title": "Status",
- "description": "Readiness status"
- },
- "uptime_seconds": {
- "type": "integer",
- "title": "Uptime Seconds",
- "description": "Server uptime in seconds"
- }
- },
- "type": "object",
- "required": [
- "status",
- "uptime_seconds"
- ],
- "title": "ReadinessResponse",
- "description": "Response model for readiness probe."
- },
"ReleaseResourcesCommandEvent": {
"properties": {
"event_id": {
@@ -15034,17 +14553,31 @@
],
"title": "ServiceUnhealthyEvent"
},
- "SessionSummary": {
+ "SessionConfigSummary": {
"properties": {
- "session_id": {
- "type": "string",
- "title": "Session Id"
- },
"replay_type": {
"$ref": "#/components/schemas/ReplayType"
},
"target": {
"$ref": "#/components/schemas/ReplayTarget"
+ }
+ },
+ "type": "object",
+ "required": [
+ "replay_type",
+ "target"
+ ],
+ "title": "SessionConfigSummary",
+ "description": "Lightweight config included in session listings."
+ },
+ "SessionSummary": {
+ "properties": {
+ "session_id": {
+ "type": "string",
+ "title": "Session Id"
+ },
+ "config": {
+ "$ref": "#/components/schemas/SessionConfigSummary"
},
"status": {
"$ref": "#/components/schemas/ReplayStatus"
@@ -15122,8 +14655,7 @@
"type": "object",
"required": [
"session_id",
- "replay_type",
- "target",
+ "config",
"status",
"total_events",
"replayed_events",
@@ -15457,59 +14989,6 @@
"title": "ThemeUpdateRequest",
"description": "Request model for theme update"
},
- "TokenValidationResponse": {
- "properties": {
- "valid": {
- "type": "boolean",
- "title": "Valid"
- },
- "username": {
- "type": "string",
- "title": "Username"
- },
- "role": {
- "type": "string",
- "title": "Role"
- },
- "csrf_token": {
- "type": "string",
- "title": "Csrf Token"
- }
- },
- "type": "object",
- "required": [
- "valid",
- "username",
- "role",
- "csrf_token"
- ],
- "title": "TokenValidationResponse",
- "description": "Response model for token validation"
- },
- "TopicStatistic": {
- "properties": {
- "topic": {
- "type": "string",
- "title": "Topic"
- },
- "count": {
- "type": "integer",
- "title": "Count"
- },
- "avg_retry_count": {
- "type": "number",
- "title": "Avg Retry Count"
- }
- },
- "type": "object",
- "required": [
- "topic",
- "count",
- "avg_retry_count"
- ],
- "title": "TopicStatistic",
- "description": "Statistics for a single topic."
- },
"UnreadCountResponse": {
"properties": {
"unread_count": {
@@ -16082,13 +15561,11 @@
"title": "Email"
},
"role": {
- "$ref": "#/components/schemas/UserRole",
- "default": "user"
+ "$ref": "#/components/schemas/UserRole"
},
"is_active": {
"type": "boolean",
- "title": "Is Active",
- "default": true
+ "title": "Is Active"
},
"user_id": {
"type": "string",
@@ -16096,8 +15573,7 @@
},
"is_superuser": {
"type": "boolean",
- "title": "Is Superuser",
- "default": false
+ "title": "Is Superuser"
},
"created_at": {
"type": "string",
@@ -16147,7 +15623,10 @@
"required": [
"username",
"email",
+ "role",
+ "is_active",
"user_id",
+ "is_superuser",
"created_at",
"updated_at"
],
diff --git a/frontend/src/lib/api-interceptors.ts b/frontend/src/lib/api-interceptors.ts
index c6666c8a..4d9525b3 100644
--- a/frontend/src/lib/api-interceptors.ts
+++ b/frontend/src/lib/api-interceptors.ts
@@ -4,7 +4,7 @@ import { goto } from '@mateothegreat/svelte5-router';
import { authStore } from '$stores/auth.svelte';
let isHandling401 = false;
-const AUTH_ENDPOINTS = ['/api/v1/auth/login', '/api/v1/auth/register', '/api/v1/auth/verify-token'];
+const AUTH_ENDPOINTS = ['/api/v1/auth/login', '/api/v1/auth/register'];
const STATUS_MESSAGES: Record = {
403: { message: 'Access denied.', type: 'error' },
diff --git a/frontend/src/lib/api/index.ts b/frontend/src/lib/api/index.ts
index b3712aae..21b4336a 100644
--- a/frontend/src/lib/api/index.ts
+++ b/frontend/src/lib/api/index.ts
@@ -1,4 +1,4 @@
// This file is auto-generated by @hey-api/openapi-ts
-export { aggregateEventsApiV1EventsAggregatePost, browseEventsApiV1AdminEventsBrowsePost, cancelExecutionApiV1ExecutionsExecutionIdCancelPost, cancelReplaySessionApiV1ReplaySessionsSessionIdCancelPost, cancelSagaApiV1SagasSagaIdCancelPost, cleanupOldSessionsApiV1ReplayCleanupPost, createExecutionApiV1ExecutePost, createReplaySessionApiV1ReplaySessionsPost, createSavedScriptApiV1ScriptsPost, createUserApiV1AdminUsersPost, deleteEventApiV1AdminEventsEventIdDelete, deleteEventApiV1EventsEventIdDelete, deleteExecutionApiV1ExecutionsExecutionIdDelete, deleteNotificationApiV1NotificationsNotificationIdDelete, deleteSavedScriptApiV1ScriptsScriptIdDelete, deleteUserApiV1AdminUsersUserIdDelete, discardDlqMessageApiV1DlqMessagesEventIdDelete, executionEventsApiV1EventsExecutionsExecutionIdGet, exportEventsCsvApiV1AdminEventsExportCsvGet, exportEventsJsonApiV1AdminEventsExportJsonGet, getCurrentRequestEventsApiV1EventsCurrentRequestGet, getCurrentUserProfileApiV1AuthMeGet, getDlqMessageApiV1DlqMessagesEventIdGet, getDlqMessagesApiV1DlqMessagesGet, getDlqStatisticsApiV1DlqStatsGet, getDlqTopicsApiV1DlqTopicsGet, getEventApiV1EventsEventIdGet, getEventDetailApiV1AdminEventsEventIdGet, getEventsByCorrelationApiV1EventsCorrelationCorrelationIdGet, getEventStatisticsApiV1EventsStatisticsGet, getEventStatsApiV1AdminEventsStatsGet, getExampleScriptsApiV1ExampleScriptsGet, getExecutionEventsApiV1EventsExecutionsExecutionIdEventsGet, getExecutionEventsApiV1ExecutionsExecutionIdEventsGet, getExecutionSagasApiV1SagasExecutionExecutionIdGet, getK8sResourceLimitsApiV1K8sLimitsGet, getNotificationsApiV1NotificationsGet, getReplaySessionApiV1ReplaySessionsSessionIdGet, getReplayStatusApiV1AdminEventsReplaySessionIdStatusGet, getResultApiV1ExecutionsExecutionIdResultGet, getSagaStatusApiV1SagasSagaIdGet, getSavedScriptApiV1ScriptsScriptIdGet, getSettingsHistoryApiV1UserSettingsHistoryGet, getSubscriptionsApiV1NotificationsSubscriptionsGet, getSystemSettingsApiV1AdminSettingsGet, getUnreadCountApiV1NotificationsUnreadCountGet, getUserApiV1AdminUsersUserIdGet, getUserEventsApiV1EventsUserGet, getUserExecutionsApiV1UserExecutionsGet, getUserOverviewApiV1AdminUsersUserIdOverviewGet, getUserRateLimitsApiV1AdminUsersUserIdRateLimitsGet, getUserSettingsApiV1UserSettingsGet, listEventTypesApiV1EventsTypesListGet, listReplaySessionsApiV1ReplaySessionsGet, listSagasApiV1SagasGet, listSavedScriptsApiV1ScriptsGet, listUsersApiV1AdminUsersGet, livenessApiV1HealthLiveGet, loginApiV1AuthLoginPost, logoutApiV1AuthLogoutPost, markAllReadApiV1NotificationsMarkAllReadPost, markNotificationReadApiV1NotificationsNotificationIdReadPut, notificationStreamApiV1EventsNotificationsStreamGet, type Options, pauseReplaySessionApiV1ReplaySessionsSessionIdPausePost, publishCustomEventApiV1EventsPublishPost, queryEventsApiV1EventsQueryPost, readinessApiV1HealthReadyGet, receiveGrafanaAlertsApiV1AlertsGrafanaPost, registerApiV1AuthRegisterPost, replayAggregateEventsApiV1EventsReplayAggregateIdPost, replayEventsApiV1AdminEventsReplayPost, resetSystemSettingsApiV1AdminSettingsResetPost, resetUserPasswordApiV1AdminUsersUserIdResetPasswordPost, resetUserRateLimitsApiV1AdminUsersUserIdRateLimitsResetPost, restoreSettingsApiV1UserSettingsRestorePost, resumeReplaySessionApiV1ReplaySessionsSessionIdResumePost, retryDlqMessagesApiV1DlqRetryPost, retryExecutionApiV1ExecutionsExecutionIdRetryPost, setRetryPolicyApiV1DlqRetryPolicyPost, startReplaySessionApiV1ReplaySessionsSessionIdStartPost, testGrafanaAlertEndpointApiV1AlertsGrafanaTestGet, updateCustomSettingApiV1UserSettingsCustomKeyPut, updateEditorSettingsApiV1UserSettingsEditorPut, updateNotificationSettingsApiV1UserSettingsNotificationsPut, updateSavedScriptApiV1ScriptsScriptIdPut, updateSubscriptionApiV1NotificationsSubscriptionsChannelPut, updateSystemSettingsApiV1AdminSettingsPut, updateThemeApiV1UserSettingsThemePut, updateUserApiV1AdminUsersUserIdPut, updateUserRateLimitsApiV1AdminUsersUserIdRateLimitsPut, updateUserSettingsApiV1UserSettingsPut, verifyTokenApiV1AuthVerifyTokenGet } from './sdk.gen';
-export type { AdminUserOverview, AgeStatistics, AggregateEventsApiV1EventsAggregatePostData, AggregateEventsApiV1EventsAggregatePostError, AggregateEventsApiV1EventsAggregatePostErrors, AggregateEventsApiV1EventsAggregatePostResponse, AggregateEventsApiV1EventsAggregatePostResponses, AlertResponse, AllocateResourcesCommandEvent, AuthFailedEvent, BodyLoginApiV1AuthLoginPost, BrowseEventsApiV1AdminEventsBrowsePostData, BrowseEventsApiV1AdminEventsBrowsePostError, BrowseEventsApiV1AdminEventsBrowsePostErrors, BrowseEventsApiV1AdminEventsBrowsePostResponse, BrowseEventsApiV1AdminEventsBrowsePostResponses, CancelExecutionApiV1ExecutionsExecutionIdCancelPostData, CancelExecutionApiV1ExecutionsExecutionIdCancelPostError, CancelExecutionApiV1ExecutionsExecutionIdCancelPostErrors, CancelExecutionApiV1ExecutionsExecutionIdCancelPostResponse, CancelExecutionApiV1ExecutionsExecutionIdCancelPostResponses, CancelExecutionRequest, CancelReplaySessionApiV1ReplaySessionsSessionIdCancelPostData, CancelReplaySessionApiV1ReplaySessionsSessionIdCancelPostError, CancelReplaySessionApiV1ReplaySessionsSessionIdCancelPostErrors, CancelReplaySessionApiV1ReplaySessionsSessionIdCancelPostResponse, CancelReplaySessionApiV1ReplaySessionsSessionIdCancelPostResponses, CancelResponse, CancelSagaApiV1SagasSagaIdCancelPostData, CancelSagaApiV1SagasSagaIdCancelPostError, CancelSagaApiV1SagasSagaIdCancelPostErrors, CancelSagaApiV1SagasSagaIdCancelPostResponse, CancelSagaApiV1SagasSagaIdCancelPostResponses, CleanupOldSessionsApiV1ReplayCleanupPostData, CleanupOldSessionsApiV1ReplayCleanupPostError, CleanupOldSessionsApiV1ReplayCleanupPostErrors, CleanupOldSessionsApiV1ReplayCleanupPostResponse, CleanupOldSessionsApiV1ReplayCleanupPostResponses, CleanupResponse, ClientOptions, ContainerStatusInfo, CreateExecutionApiV1ExecutePostData, CreateExecutionApiV1ExecutePostError, CreateExecutionApiV1ExecutePostErrors, CreateExecutionApiV1ExecutePostResponse, CreateExecutionApiV1ExecutePostResponses, CreatePodCommandEvent, CreateReplaySessionApiV1ReplaySessionsPostData, CreateReplaySessionApiV1ReplaySessionsPostError, CreateReplaySessionApiV1ReplaySessionsPostErrors, CreateReplaySessionApiV1ReplaySessionsPostResponse, CreateReplaySessionApiV1ReplaySessionsPostResponses, CreateSavedScriptApiV1ScriptsPostData, CreateSavedScriptApiV1ScriptsPostError, CreateSavedScriptApiV1ScriptsPostErrors, CreateSavedScriptApiV1ScriptsPostResponse, CreateSavedScriptApiV1ScriptsPostResponses, CreateUserApiV1AdminUsersPostData, CreateUserApiV1AdminUsersPostError, CreateUserApiV1AdminUsersPostErrors, CreateUserApiV1AdminUsersPostResponse, CreateUserApiV1AdminUsersPostResponses, DeleteEventApiV1AdminEventsEventIdDeleteData, DeleteEventApiV1AdminEventsEventIdDeleteError, DeleteEventApiV1AdminEventsEventIdDeleteErrors, DeleteEventApiV1AdminEventsEventIdDeleteResponse, DeleteEventApiV1AdminEventsEventIdDeleteResponses, DeleteEventApiV1EventsEventIdDeleteData, DeleteEventApiV1EventsEventIdDeleteError, DeleteEventApiV1EventsEventIdDeleteErrors, DeleteEventApiV1EventsEventIdDeleteResponse, DeleteEventApiV1EventsEventIdDeleteResponses, DeleteEventResponse, DeleteExecutionApiV1ExecutionsExecutionIdDeleteData, DeleteExecutionApiV1ExecutionsExecutionIdDeleteError, DeleteExecutionApiV1ExecutionsExecutionIdDeleteErrors, DeleteExecutionApiV1ExecutionsExecutionIdDeleteResponse, DeleteExecutionApiV1ExecutionsExecutionIdDeleteResponses, DeleteNotificationApiV1NotificationsNotificationIdDeleteData, DeleteNotificationApiV1NotificationsNotificationIdDeleteError, DeleteNotificationApiV1NotificationsNotificationIdDeleteErrors, DeleteNotificationApiV1NotificationsNotificationIdDeleteResponse, DeleteNotificationApiV1NotificationsNotificationIdDeleteResponses, DeleteNotificationResponse, DeletePodCommandEvent, DeleteResponse, DeleteSavedScriptApiV1ScriptsScriptIdDeleteData, DeleteSavedScriptApiV1ScriptsScriptIdDeleteError, DeleteSavedScriptApiV1ScriptsScriptIdDeleteErrors, DeleteSavedScriptApiV1ScriptsScriptIdDeleteResponse, DeleteSavedScriptApiV1ScriptsScriptIdDeleteResponses, DeleteUserApiV1AdminUsersUserIdDeleteData, DeleteUserApiV1AdminUsersUserIdDeleteError, DeleteUserApiV1AdminUsersUserIdDeleteErrors, DeleteUserApiV1AdminUsersUserIdDeleteResponse, DeleteUserApiV1AdminUsersUserIdDeleteResponses, DeleteUserResponse, DerivedCounts, DiscardDlqMessageApiV1DlqMessagesEventIdDeleteData, DiscardDlqMessageApiV1DlqMessagesEventIdDeleteError, DiscardDlqMessageApiV1DlqMessagesEventIdDeleteErrors, DiscardDlqMessageApiV1DlqMessagesEventIdDeleteResponse, DiscardDlqMessageApiV1DlqMessagesEventIdDeleteResponses, DlqBatchRetryResponse, DlqMessageDetail, DlqMessageDiscardedEvent, DlqMessageReceivedEvent, DlqMessageResponse, DlqMessageRetriedEvent, DlqMessagesResponse, DlqMessageStatus, DlqRetryResult, DlqStats, DlqTopicSummaryResponse, EditorSettings, EndpointGroup, EndpointUsageStats, Environment, ErrorResponse, EventAggregationRequest, EventBrowseRequest, EventBrowseResponse, EventDeleteResponse, EventDetailResponse, EventFilter, EventFilterRequest, EventListResponse, EventMetadata, EventReplayRequest, EventReplayResponse, EventReplayStatusResponse, EventReplayStatusResponseWritable, EventStatistics, EventStatsResponse, EventSummary, EventType, EventTypeCountSchema, EventTypeStatistic, ExampleScripts, ExecutionAcceptedEvent, ExecutionCancelledEvent, ExecutionCompletedEvent, ExecutionErrorType, ExecutionEventsApiV1EventsExecutionsExecutionIdGetData, ExecutionEventsApiV1EventsExecutionsExecutionIdGetError, ExecutionEventsApiV1EventsExecutionsExecutionIdGetErrors, ExecutionEventsApiV1EventsExecutionsExecutionIdGetResponse, ExecutionEventsApiV1EventsExecutionsExecutionIdGetResponses, ExecutionFailedEvent, ExecutionLimitsSchema, ExecutionListResponse, ExecutionQueuedEvent, ExecutionRequest, ExecutionRequestedEvent, ExecutionResponse, ExecutionResult, ExecutionRunningEvent, ExecutionStartedEvent, ExecutionStatus, ExecutionTimeoutEvent, ExportEventsCsvApiV1AdminEventsExportCsvGetData, ExportEventsCsvApiV1AdminEventsExportCsvGetError, ExportEventsCsvApiV1AdminEventsExportCsvGetErrors, ExportEventsCsvApiV1AdminEventsExportCsvGetResponses, ExportEventsJsonApiV1AdminEventsExportJsonGetData, ExportEventsJsonApiV1AdminEventsExportJsonGetError, ExportEventsJsonApiV1AdminEventsExportJsonGetErrors, ExportEventsJsonApiV1AdminEventsExportJsonGetResponses, GetCurrentRequestEventsApiV1EventsCurrentRequestGetData, GetCurrentRequestEventsApiV1EventsCurrentRequestGetError, GetCurrentRequestEventsApiV1EventsCurrentRequestGetErrors, GetCurrentRequestEventsApiV1EventsCurrentRequestGetResponse, GetCurrentRequestEventsApiV1EventsCurrentRequestGetResponses, GetCurrentUserProfileApiV1AuthMeGetData, GetCurrentUserProfileApiV1AuthMeGetResponse, GetCurrentUserProfileApiV1AuthMeGetResponses, GetDlqMessageApiV1DlqMessagesEventIdGetData, GetDlqMessageApiV1DlqMessagesEventIdGetError, GetDlqMessageApiV1DlqMessagesEventIdGetErrors, GetDlqMessageApiV1DlqMessagesEventIdGetResponse, GetDlqMessageApiV1DlqMessagesEventIdGetResponses, GetDlqMessagesApiV1DlqMessagesGetData, GetDlqMessagesApiV1DlqMessagesGetError, GetDlqMessagesApiV1DlqMessagesGetErrors, GetDlqMessagesApiV1DlqMessagesGetResponse, GetDlqMessagesApiV1DlqMessagesGetResponses, GetDlqStatisticsApiV1DlqStatsGetData, GetDlqStatisticsApiV1DlqStatsGetResponse, GetDlqStatisticsApiV1DlqStatsGetResponses, GetDlqTopicsApiV1DlqTopicsGetData, GetDlqTopicsApiV1DlqTopicsGetResponse, GetDlqTopicsApiV1DlqTopicsGetResponses, GetEventApiV1EventsEventIdGetData, GetEventApiV1EventsEventIdGetError, GetEventApiV1EventsEventIdGetErrors, GetEventApiV1EventsEventIdGetResponse, GetEventApiV1EventsEventIdGetResponses, GetEventDetailApiV1AdminEventsEventIdGetData, GetEventDetailApiV1AdminEventsEventIdGetError, GetEventDetailApiV1AdminEventsEventIdGetErrors, GetEventDetailApiV1AdminEventsEventIdGetResponse, GetEventDetailApiV1AdminEventsEventIdGetResponses, GetEventsByCorrelationApiV1EventsCorrelationCorrelationIdGetData, GetEventsByCorrelationApiV1EventsCorrelationCorrelationIdGetError, GetEventsByCorrelationApiV1EventsCorrelationCorrelationIdGetErrors, GetEventsByCorrelationApiV1EventsCorrelationCorrelationIdGetResponse, GetEventsByCorrelationApiV1EventsCorrelationCorrelationIdGetResponses, GetEventStatisticsApiV1EventsStatisticsGetData, GetEventStatisticsApiV1EventsStatisticsGetError, GetEventStatisticsApiV1EventsStatisticsGetErrors, GetEventStatisticsApiV1EventsStatisticsGetResponse, GetEventStatisticsApiV1EventsStatisticsGetResponses, GetEventStatsApiV1AdminEventsStatsGetData, GetEventStatsApiV1AdminEventsStatsGetError, GetEventStatsApiV1AdminEventsStatsGetErrors, GetEventStatsApiV1AdminEventsStatsGetResponse, GetEventStatsApiV1AdminEventsStatsGetResponses, GetExampleScriptsApiV1ExampleScriptsGetData, GetExampleScriptsApiV1ExampleScriptsGetResponse, GetExampleScriptsApiV1ExampleScriptsGetResponses, GetExecutionEventsApiV1EventsExecutionsExecutionIdEventsGetData, GetExecutionEventsApiV1EventsExecutionsExecutionIdEventsGetError, GetExecutionEventsApiV1EventsExecutionsExecutionIdEventsGetErrors, GetExecutionEventsApiV1EventsExecutionsExecutionIdEventsGetResponse, GetExecutionEventsApiV1EventsExecutionsExecutionIdEventsGetResponses, GetExecutionEventsApiV1ExecutionsExecutionIdEventsGetData, GetExecutionEventsApiV1ExecutionsExecutionIdEventsGetError, GetExecutionEventsApiV1ExecutionsExecutionIdEventsGetErrors, GetExecutionEventsApiV1ExecutionsExecutionIdEventsGetResponse, GetExecutionEventsApiV1ExecutionsExecutionIdEventsGetResponses, GetExecutionSagasApiV1SagasExecutionExecutionIdGetData, GetExecutionSagasApiV1SagasExecutionExecutionIdGetError, GetExecutionSagasApiV1SagasExecutionExecutionIdGetErrors, GetExecutionSagasApiV1SagasExecutionExecutionIdGetResponse, GetExecutionSagasApiV1SagasExecutionExecutionIdGetResponses, GetK8sResourceLimitsApiV1K8sLimitsGetData, GetK8sResourceLimitsApiV1K8sLimitsGetResponse, GetK8sResourceLimitsApiV1K8sLimitsGetResponses, GetNotificationsApiV1NotificationsGetData, GetNotificationsApiV1NotificationsGetError, GetNotificationsApiV1NotificationsGetErrors, GetNotificationsApiV1NotificationsGetResponse, GetNotificationsApiV1NotificationsGetResponses, GetReplaySessionApiV1ReplaySessionsSessionIdGetData, GetReplaySessionApiV1ReplaySessionsSessionIdGetError, GetReplaySessionApiV1ReplaySessionsSessionIdGetErrors, GetReplaySessionApiV1ReplaySessionsSessionIdGetResponse, GetReplaySessionApiV1ReplaySessionsSessionIdGetResponses, GetReplayStatusApiV1AdminEventsReplaySessionIdStatusGetData, GetReplayStatusApiV1AdminEventsReplaySessionIdStatusGetError, GetReplayStatusApiV1AdminEventsReplaySessionIdStatusGetErrors, GetReplayStatusApiV1AdminEventsReplaySessionIdStatusGetResponse, GetReplayStatusApiV1AdminEventsReplaySessionIdStatusGetResponses, GetResultApiV1ExecutionsExecutionIdResultGetData, GetResultApiV1ExecutionsExecutionIdResultGetError, GetResultApiV1ExecutionsExecutionIdResultGetErrors, GetResultApiV1ExecutionsExecutionIdResultGetResponse, GetResultApiV1ExecutionsExecutionIdResultGetResponses, GetSagaStatusApiV1SagasSagaIdGetData, GetSagaStatusApiV1SagasSagaIdGetError, GetSagaStatusApiV1SagasSagaIdGetErrors, GetSagaStatusApiV1SagasSagaIdGetResponse, GetSagaStatusApiV1SagasSagaIdGetResponses, GetSavedScriptApiV1ScriptsScriptIdGetData, GetSavedScriptApiV1ScriptsScriptIdGetError, GetSavedScriptApiV1ScriptsScriptIdGetErrors, GetSavedScriptApiV1ScriptsScriptIdGetResponse, GetSavedScriptApiV1ScriptsScriptIdGetResponses, GetSettingsHistoryApiV1UserSettingsHistoryGetData, GetSettingsHistoryApiV1UserSettingsHistoryGetError, GetSettingsHistoryApiV1UserSettingsHistoryGetErrors, GetSettingsHistoryApiV1UserSettingsHistoryGetResponse, GetSettingsHistoryApiV1UserSettingsHistoryGetResponses, GetSubscriptionsApiV1NotificationsSubscriptionsGetData, GetSubscriptionsApiV1NotificationsSubscriptionsGetResponse, GetSubscriptionsApiV1NotificationsSubscriptionsGetResponses, GetSystemSettingsApiV1AdminSettingsGetData, GetSystemSettingsApiV1AdminSettingsGetError, GetSystemSettingsApiV1AdminSettingsGetErrors, GetSystemSettingsApiV1AdminSettingsGetResponse, GetSystemSettingsApiV1AdminSettingsGetResponses, GetUnreadCountApiV1NotificationsUnreadCountGetData, GetUnreadCountApiV1NotificationsUnreadCountGetResponse, GetUnreadCountApiV1NotificationsUnreadCountGetResponses, GetUserApiV1AdminUsersUserIdGetData, GetUserApiV1AdminUsersUserIdGetError, GetUserApiV1AdminUsersUserIdGetErrors, GetUserApiV1AdminUsersUserIdGetResponse, GetUserApiV1AdminUsersUserIdGetResponses, GetUserEventsApiV1EventsUserGetData, GetUserEventsApiV1EventsUserGetError, GetUserEventsApiV1EventsUserGetErrors, GetUserEventsApiV1EventsUserGetResponse, GetUserEventsApiV1EventsUserGetResponses, GetUserExecutionsApiV1UserExecutionsGetData, GetUserExecutionsApiV1UserExecutionsGetError, GetUserExecutionsApiV1UserExecutionsGetErrors, GetUserExecutionsApiV1UserExecutionsGetResponse, GetUserExecutionsApiV1UserExecutionsGetResponses, GetUserOverviewApiV1AdminUsersUserIdOverviewGetData, GetUserOverviewApiV1AdminUsersUserIdOverviewGetError, GetUserOverviewApiV1AdminUsersUserIdOverviewGetErrors, GetUserOverviewApiV1AdminUsersUserIdOverviewGetResponse, GetUserOverviewApiV1AdminUsersUserIdOverviewGetResponses, GetUserRateLimitsApiV1AdminUsersUserIdRateLimitsGetData, GetUserRateLimitsApiV1AdminUsersUserIdRateLimitsGetError, GetUserRateLimitsApiV1AdminUsersUserIdRateLimitsGetErrors, GetUserRateLimitsApiV1AdminUsersUserIdRateLimitsGetResponse, GetUserRateLimitsApiV1AdminUsersUserIdRateLimitsGetResponses, GetUserSettingsApiV1UserSettingsGetData, GetUserSettingsApiV1UserSettingsGetResponse, GetUserSettingsApiV1UserSettingsGetResponses, GrafanaAlertItem, GrafanaWebhook, HourlyEventCountSchema, HttpValidationError, KafkaTopic, LanguageInfo, ListEventTypesApiV1EventsTypesListGetData, ListEventTypesApiV1EventsTypesListGetResponse, ListEventTypesApiV1EventsTypesListGetResponses, ListReplaySessionsApiV1ReplaySessionsGetData, ListReplaySessionsApiV1ReplaySessionsGetError, ListReplaySessionsApiV1ReplaySessionsGetErrors, ListReplaySessionsApiV1ReplaySessionsGetResponse, ListReplaySessionsApiV1ReplaySessionsGetResponses, ListSagasApiV1SagasGetData, ListSagasApiV1SagasGetError, ListSagasApiV1SagasGetErrors, ListSagasApiV1SagasGetResponse, ListSagasApiV1SagasGetResponses, ListSavedScriptsApiV1ScriptsGetData, ListSavedScriptsApiV1ScriptsGetResponse, ListSavedScriptsApiV1ScriptsGetResponses, ListUsersApiV1AdminUsersGetData, ListUsersApiV1AdminUsersGetError, ListUsersApiV1AdminUsersGetErrors, ListUsersApiV1AdminUsersGetResponse, ListUsersApiV1AdminUsersGetResponses, LivenessApiV1HealthLiveGetData, LivenessApiV1HealthLiveGetResponse, LivenessApiV1HealthLiveGetResponses, LivenessResponse, LoginApiV1AuthLoginPostData, LoginApiV1AuthLoginPostError, LoginApiV1AuthLoginPostErrors, LoginApiV1AuthLoginPostResponse, LoginApiV1AuthLoginPostResponses, LoginMethod, LoginResponse, LogoutApiV1AuthLogoutPostData, LogoutApiV1AuthLogoutPostResponse, LogoutApiV1AuthLogoutPostResponses, ManualRetryRequest, MarkAllReadApiV1NotificationsMarkAllReadPostData, MarkAllReadApiV1NotificationsMarkAllReadPostResponse, MarkAllReadApiV1NotificationsMarkAllReadPostResponses, MarkNotificationReadApiV1NotificationsNotificationIdReadPutData, MarkNotificationReadApiV1NotificationsNotificationIdReadPutError, MarkNotificationReadApiV1NotificationsNotificationIdReadPutErrors, MarkNotificationReadApiV1NotificationsNotificationIdReadPutResponse, MarkNotificationReadApiV1NotificationsNotificationIdReadPutResponses, MessageResponse, MonitoringSettingsSchema, NotificationAllReadEvent, NotificationChannel, NotificationClickedEvent, NotificationCreatedEvent, NotificationDeliveredEvent, NotificationFailedEvent, NotificationListResponse, NotificationPreferencesUpdatedEvent, NotificationReadEvent, NotificationResponse, NotificationSentEvent, NotificationSettings, NotificationSeverity, NotificationStatus, NotificationStreamApiV1EventsNotificationsStreamGetData, NotificationStreamApiV1EventsNotificationsStreamGetResponse, NotificationStreamApiV1EventsNotificationsStreamGetResponses, NotificationSubscription, PasswordResetRequest, PauseReplaySessionApiV1ReplaySessionsSessionIdPausePostData, PauseReplaySessionApiV1ReplaySessionsSessionIdPausePostError, PauseReplaySessionApiV1ReplaySessionsSessionIdPausePostErrors, PauseReplaySessionApiV1ReplaySessionsSessionIdPausePostResponse, PauseReplaySessionApiV1ReplaySessionsSessionIdPausePostResponses, PodCreatedEvent, PodDeletedEvent, PodFailedEvent, PodRunningEvent, PodScheduledEvent, PodSucceededEvent, PodTerminatedEvent, PublishCustomEventApiV1EventsPublishPostData, PublishCustomEventApiV1EventsPublishPostError, PublishCustomEventApiV1EventsPublishPostErrors, PublishCustomEventApiV1EventsPublishPostResponse, PublishCustomEventApiV1EventsPublishPostResponses, PublishEventRequest, PublishEventResponse, QueryEventsApiV1EventsQueryPostData, QueryEventsApiV1EventsQueryPostError, QueryEventsApiV1EventsQueryPostErrors, QueryEventsApiV1EventsQueryPostResponse, QueryEventsApiV1EventsQueryPostResponses, QueuePriority, QuotaExceededEvent, RateLimitAlgorithm, RateLimitExceededEvent, RateLimitRuleRequest, RateLimitRuleResponse, RateLimitSummary, RateLimitUpdateRequest, RateLimitUpdateResponse, ReadinessApiV1HealthReadyGetData, ReadinessApiV1HealthReadyGetResponse, ReadinessApiV1HealthReadyGetResponses, ReadinessResponse, ReceiveGrafanaAlertsApiV1AlertsGrafanaPostData, ReceiveGrafanaAlertsApiV1AlertsGrafanaPostError, ReceiveGrafanaAlertsApiV1AlertsGrafanaPostErrors, ReceiveGrafanaAlertsApiV1AlertsGrafanaPostResponse, ReceiveGrafanaAlertsApiV1AlertsGrafanaPostResponses, RegisterApiV1AuthRegisterPostData, RegisterApiV1AuthRegisterPostError, RegisterApiV1AuthRegisterPostErrors, RegisterApiV1AuthRegisterPostResponse, RegisterApiV1AuthRegisterPostResponses, ReleaseResourcesCommandEvent, ReplayAggregateEventsApiV1EventsReplayAggregateIdPostData, ReplayAggregateEventsApiV1EventsReplayAggregateIdPostError, ReplayAggregateEventsApiV1EventsReplayAggregateIdPostErrors, ReplayAggregateEventsApiV1EventsReplayAggregateIdPostResponse, ReplayAggregateEventsApiV1EventsReplayAggregateIdPostResponses, ReplayAggregateResponse, ReplayConfigSchema, ReplayError, ReplayEventsApiV1AdminEventsReplayPostData, ReplayEventsApiV1AdminEventsReplayPostError, ReplayEventsApiV1AdminEventsReplayPostErrors, ReplayEventsApiV1AdminEventsReplayPostResponse, ReplayEventsApiV1AdminEventsReplayPostResponses, ReplayFilter, ReplayFilterSchema, ReplayRequest, ReplayResponse, ReplaySession, ReplayStatus, ReplayTarget, ReplayType, ResetSystemSettingsApiV1AdminSettingsResetPostData, ResetSystemSettingsApiV1AdminSettingsResetPostError, ResetSystemSettingsApiV1AdminSettingsResetPostErrors, ResetSystemSettingsApiV1AdminSettingsResetPostResponse, ResetSystemSettingsApiV1AdminSettingsResetPostResponses, ResetUserPasswordApiV1AdminUsersUserIdResetPasswordPostData, ResetUserPasswordApiV1AdminUsersUserIdResetPasswordPostError, ResetUserPasswordApiV1AdminUsersUserIdResetPasswordPostErrors, ResetUserPasswordApiV1AdminUsersUserIdResetPasswordPostResponse, ResetUserPasswordApiV1AdminUsersUserIdResetPasswordPostResponses, ResetUserRateLimitsApiV1AdminUsersUserIdRateLimitsResetPostData, ResetUserRateLimitsApiV1AdminUsersUserIdRateLimitsResetPostError, ResetUserRateLimitsApiV1AdminUsersUserIdRateLimitsResetPostErrors, ResetUserRateLimitsApiV1AdminUsersUserIdRateLimitsResetPostResponse, ResetUserRateLimitsApiV1AdminUsersUserIdRateLimitsResetPostResponses, ResourceLimitExceededEvent, ResourceLimits, ResourceUsage, ResourceUsageDomain, RestoreSettingsApiV1UserSettingsRestorePostData, RestoreSettingsApiV1UserSettingsRestorePostError, RestoreSettingsApiV1UserSettingsRestorePostErrors, RestoreSettingsApiV1UserSettingsRestorePostResponse, RestoreSettingsApiV1UserSettingsRestorePostResponses, RestoreSettingsRequest, ResultFailedEvent, ResultStoredEvent, ResumeReplaySessionApiV1ReplaySessionsSessionIdResumePostData, ResumeReplaySessionApiV1ReplaySessionsSessionIdResumePostError, ResumeReplaySessionApiV1ReplaySessionsSessionIdResumePostErrors, ResumeReplaySessionApiV1ReplaySessionsSessionIdResumePostResponse, ResumeReplaySessionApiV1ReplaySessionsSessionIdResumePostResponses, RetryDlqMessagesApiV1DlqRetryPostData, RetryDlqMessagesApiV1DlqRetryPostError, RetryDlqMessagesApiV1DlqRetryPostErrors, RetryDlqMessagesApiV1DlqRetryPostResponse, RetryDlqMessagesApiV1DlqRetryPostResponses, RetryExecutionApiV1ExecutionsExecutionIdRetryPostData, RetryExecutionApiV1ExecutionsExecutionIdRetryPostError, RetryExecutionApiV1ExecutionsExecutionIdRetryPostErrors, RetryExecutionApiV1ExecutionsExecutionIdRetryPostResponse, RetryExecutionApiV1ExecutionsExecutionIdRetryPostResponses, RetryExecutionRequest, RetryPolicyRequest, RetryStrategy, SagaCancellationResponse, SagaCancelledEvent, SagaCompensatedEvent, SagaCompensatingEvent, SagaCompletedEvent, SagaFailedEvent, SagaListResponse, SagaStartedEvent, SagaState, SagaStatusResponse, SavedScriptCreateRequest, SavedScriptResponse, SavedScriptUpdate, ScriptDeletedEvent, ScriptSavedEvent, ScriptSharedEvent, SecuritySettingsSchema, SecurityViolationEvent, ServiceEventCountSchema, ServiceRecoveredEvent, ServiceUnhealthyEvent, SessionSummary, SessionSummaryWritable, SetRetryPolicyApiV1DlqRetryPolicyPostData, SetRetryPolicyApiV1DlqRetryPolicyPostError, SetRetryPolicyApiV1DlqRetryPolicyPostErrors, SetRetryPolicyApiV1DlqRetryPolicyPostResponse, SetRetryPolicyApiV1DlqRetryPolicyPostResponses, SettingsHistoryEntry, SettingsHistoryResponse, SortOrder, SseControlEvent, SseExecutionEventData, StartReplaySessionApiV1ReplaySessionsSessionIdStartPostData, StartReplaySessionApiV1ReplaySessionsSessionIdStartPostError, StartReplaySessionApiV1ReplaySessionsSessionIdStartPostErrors, StartReplaySessionApiV1ReplaySessionsSessionIdStartPostResponse, StartReplaySessionApiV1ReplaySessionsSessionIdStartPostResponses, StorageType, SubscriptionsResponse, SubscriptionUpdate, SystemErrorEvent, SystemSettings, TestGrafanaAlertEndpointApiV1AlertsGrafanaTestGetData, TestGrafanaAlertEndpointApiV1AlertsGrafanaTestGetResponse, TestGrafanaAlertEndpointApiV1AlertsGrafanaTestGetResponses, Theme, ThemeUpdateRequest, TokenValidationResponse, TopicStatistic, UnreadCountResponse, UpdateCustomSettingApiV1UserSettingsCustomKeyPutData, UpdateCustomSettingApiV1UserSettingsCustomKeyPutError, UpdateCustomSettingApiV1UserSettingsCustomKeyPutErrors, UpdateCustomSettingApiV1UserSettingsCustomKeyPutResponse, UpdateCustomSettingApiV1UserSettingsCustomKeyPutResponses, UpdateEditorSettingsApiV1UserSettingsEditorPutData, UpdateEditorSettingsApiV1UserSettingsEditorPutError, UpdateEditorSettingsApiV1UserSettingsEditorPutErrors, UpdateEditorSettingsApiV1UserSettingsEditorPutResponse, UpdateEditorSettingsApiV1UserSettingsEditorPutResponses, UpdateNotificationSettingsApiV1UserSettingsNotificationsPutData, UpdateNotificationSettingsApiV1UserSettingsNotificationsPutError, UpdateNotificationSettingsApiV1UserSettingsNotificationsPutErrors, UpdateNotificationSettingsApiV1UserSettingsNotificationsPutResponse, UpdateNotificationSettingsApiV1UserSettingsNotificationsPutResponses, UpdateSavedScriptApiV1ScriptsScriptIdPutData, UpdateSavedScriptApiV1ScriptsScriptIdPutError, UpdateSavedScriptApiV1ScriptsScriptIdPutErrors, UpdateSavedScriptApiV1ScriptsScriptIdPutResponse, UpdateSavedScriptApiV1ScriptsScriptIdPutResponses, UpdateSubscriptionApiV1NotificationsSubscriptionsChannelPutData, UpdateSubscriptionApiV1NotificationsSubscriptionsChannelPutError, UpdateSubscriptionApiV1NotificationsSubscriptionsChannelPutErrors, UpdateSubscriptionApiV1NotificationsSubscriptionsChannelPutResponse, UpdateSubscriptionApiV1NotificationsSubscriptionsChannelPutResponses, UpdateSystemSettingsApiV1AdminSettingsPutData, UpdateSystemSettingsApiV1AdminSettingsPutError, UpdateSystemSettingsApiV1AdminSettingsPutErrors, UpdateSystemSettingsApiV1AdminSettingsPutResponse, UpdateSystemSettingsApiV1AdminSettingsPutResponses, UpdateThemeApiV1UserSettingsThemePutData, UpdateThemeApiV1UserSettingsThemePutError, UpdateThemeApiV1UserSettingsThemePutErrors, UpdateThemeApiV1UserSettingsThemePutResponse, UpdateThemeApiV1UserSettingsThemePutResponses, UpdateUserApiV1AdminUsersUserIdPutData, UpdateUserApiV1AdminUsersUserIdPutError, UpdateUserApiV1AdminUsersUserIdPutErrors, UpdateUserApiV1AdminUsersUserIdPutResponse, UpdateUserApiV1AdminUsersUserIdPutResponses, UpdateUserRateLimitsApiV1AdminUsersUserIdRateLimitsPutData, UpdateUserRateLimitsApiV1AdminUsersUserIdRateLimitsPutError, UpdateUserRateLimitsApiV1AdminUsersUserIdRateLimitsPutErrors, UpdateUserRateLimitsApiV1AdminUsersUserIdRateLimitsPutResponse, UpdateUserRateLimitsApiV1AdminUsersUserIdRateLimitsPutResponses, UpdateUserSettingsApiV1UserSettingsPutData, UpdateUserSettingsApiV1UserSettingsPutError, UpdateUserSettingsApiV1UserSettingsPutErrors, UpdateUserSettingsApiV1UserSettingsPutResponse, UpdateUserSettingsApiV1UserSettingsPutResponses, UserCreate, UserDeletedEvent, UserEventCountSchema, UserListResponse, UserLoggedInEvent, UserLoggedOutEvent, UserLoginEvent, UserRateLimitConfigResponse, UserRateLimitsResponse, UserRegisteredEvent, UserResponse, UserRole, UserSettings, UserSettingsUpdate, UserSettingsUpdatedEvent, UserUpdate, UserUpdatedEvent, ValidationError, VerifyTokenApiV1AuthVerifyTokenGetData, VerifyTokenApiV1AuthVerifyTokenGetError, VerifyTokenApiV1AuthVerifyTokenGetErrors, VerifyTokenApiV1AuthVerifyTokenGetResponse, VerifyTokenApiV1AuthVerifyTokenGetResponses } from './types.gen';
+export { browseEventsApiV1AdminEventsBrowsePost, cancelExecutionApiV1ExecutionsExecutionIdCancelPost, cancelReplaySessionApiV1ReplaySessionsSessionIdCancelPost, cancelSagaApiV1SagasSagaIdCancelPost, cleanupOldSessionsApiV1ReplayCleanupPost, createExecutionApiV1ExecutePost, createReplaySessionApiV1ReplaySessionsPost, createSavedScriptApiV1ScriptsPost, createUserApiV1AdminUsersPost, deleteEventApiV1AdminEventsEventIdDelete, deleteEventApiV1EventsEventIdDelete, deleteExecutionApiV1ExecutionsExecutionIdDelete, deleteNotificationApiV1NotificationsNotificationIdDelete, deleteSavedScriptApiV1ScriptsScriptIdDelete, deleteUserApiV1AdminUsersUserIdDelete, discardDlqMessageApiV1DlqMessagesEventIdDelete, executionEventsApiV1EventsExecutionsExecutionIdGet, exportEventsCsvApiV1AdminEventsExportCsvGet, exportEventsJsonApiV1AdminEventsExportJsonGet, getCurrentRequestEventsApiV1EventsCurrentRequestGet, getCurrentUserProfileApiV1AuthMeGet, getDlqMessageApiV1DlqMessagesEventIdGet, getDlqMessagesApiV1DlqMessagesGet, getDlqTopicsApiV1DlqTopicsGet, getEventApiV1EventsEventIdGet, getEventDetailApiV1AdminEventsEventIdGet, getEventsByCorrelationApiV1EventsCorrelationCorrelationIdGet, getEventStatisticsApiV1EventsStatisticsGet, getEventStatsApiV1AdminEventsStatsGet, getExampleScriptsApiV1ExampleScriptsGet, getExecutionEventsApiV1EventsExecutionsExecutionIdEventsGet, getExecutionEventsApiV1ExecutionsExecutionIdEventsGet, getExecutionSagasApiV1SagasExecutionExecutionIdGet, getK8sResourceLimitsApiV1K8sLimitsGet, getNotificationsApiV1NotificationsGet, getReplaySessionApiV1ReplaySessionsSessionIdGet, getReplayStatusApiV1AdminEventsReplaySessionIdStatusGet, getResultApiV1ExecutionsExecutionIdResultGet, getSagaStatusApiV1SagasSagaIdGet, getSavedScriptApiV1ScriptsScriptIdGet, getSettingsHistoryApiV1UserSettingsHistoryGet, getSubscriptionsApiV1NotificationsSubscriptionsGet, getSystemSettingsApiV1AdminSettingsGet, getUnreadCountApiV1NotificationsUnreadCountGet, getUserApiV1AdminUsersUserIdGet, getUserEventsApiV1EventsUserGet, getUserExecutionsApiV1UserExecutionsGet, getUserOverviewApiV1AdminUsersUserIdOverviewGet, getUserRateLimitsApiV1AdminUsersUserIdRateLimitsGet, getUserSettingsApiV1UserSettingsGet, listReplaySessionsApiV1ReplaySessionsGet, listSagasApiV1SagasGet, listSavedScriptsApiV1ScriptsGet, listUsersApiV1AdminUsersGet, livenessApiV1HealthLiveGet, loginApiV1AuthLoginPost, logoutApiV1AuthLogoutPost, markAllReadApiV1NotificationsMarkAllReadPost, markNotificationReadApiV1NotificationsNotificationIdReadPut, notificationStreamApiV1EventsNotificationsStreamGet, type Options, pauseReplaySessionApiV1ReplaySessionsSessionIdPausePost, publishCustomEventApiV1EventsPublishPost, queryEventsApiV1EventsQueryPost, registerApiV1AuthRegisterPost, replayAggregateEventsApiV1EventsReplayAggregateIdPost, replayEventsApiV1AdminEventsReplayPost, resetSystemSettingsApiV1AdminSettingsResetPost, resetUserPasswordApiV1AdminUsersUserIdResetPasswordPost, resetUserRateLimitsApiV1AdminUsersUserIdRateLimitsResetPost, restoreSettingsApiV1UserSettingsRestorePost, resumeReplaySessionApiV1ReplaySessionsSessionIdResumePost, retryDlqMessagesApiV1DlqRetryPost, retryExecutionApiV1ExecutionsExecutionIdRetryPost, setRetryPolicyApiV1DlqRetryPolicyPost, startReplaySessionApiV1ReplaySessionsSessionIdStartPost, updateCustomSettingApiV1UserSettingsCustomKeyPut, updateEditorSettingsApiV1UserSettingsEditorPut, updateNotificationSettingsApiV1UserSettingsNotificationsPut, updateSavedScriptApiV1ScriptsScriptIdPut, updateSubscriptionApiV1NotificationsSubscriptionsChannelPut, updateSystemSettingsApiV1AdminSettingsPut, updateThemeApiV1UserSettingsThemePut, updateUserApiV1AdminUsersUserIdPut, updateUserRateLimitsApiV1AdminUsersUserIdRateLimitsPut, updateUserSettingsApiV1UserSettingsPut } from './sdk.gen';
+export type { AdminUserOverview, AllocateResourcesCommandEvent, AuthFailedEvent, BodyLoginApiV1AuthLoginPost, BrowseEventsApiV1AdminEventsBrowsePostData, BrowseEventsApiV1AdminEventsBrowsePostError, BrowseEventsApiV1AdminEventsBrowsePostErrors, BrowseEventsApiV1AdminEventsBrowsePostResponse, BrowseEventsApiV1AdminEventsBrowsePostResponses, CancelExecutionApiV1ExecutionsExecutionIdCancelPostData, CancelExecutionApiV1ExecutionsExecutionIdCancelPostError, CancelExecutionApiV1ExecutionsExecutionIdCancelPostErrors, CancelExecutionApiV1ExecutionsExecutionIdCancelPostResponse, CancelExecutionApiV1ExecutionsExecutionIdCancelPostResponses, CancelExecutionRequest, CancelReplaySessionApiV1ReplaySessionsSessionIdCancelPostData, CancelReplaySessionApiV1ReplaySessionsSessionIdCancelPostError, CancelReplaySessionApiV1ReplaySessionsSessionIdCancelPostErrors, CancelReplaySessionApiV1ReplaySessionsSessionIdCancelPostResponse, CancelReplaySessionApiV1ReplaySessionsSessionIdCancelPostResponses, CancelResponse, CancelSagaApiV1SagasSagaIdCancelPostData, CancelSagaApiV1SagasSagaIdCancelPostError, CancelSagaApiV1SagasSagaIdCancelPostErrors, CancelSagaApiV1SagasSagaIdCancelPostResponse, CancelSagaApiV1SagasSagaIdCancelPostResponses, CleanupOldSessionsApiV1ReplayCleanupPostData, CleanupOldSessionsApiV1ReplayCleanupPostError, CleanupOldSessionsApiV1ReplayCleanupPostErrors, CleanupOldSessionsApiV1ReplayCleanupPostResponse, CleanupOldSessionsApiV1ReplayCleanupPostResponses, CleanupResponse, ClientOptions, ContainerStatusInfo, CreateExecutionApiV1ExecutePostData, CreateExecutionApiV1ExecutePostError, CreateExecutionApiV1ExecutePostErrors, CreateExecutionApiV1ExecutePostResponse, CreateExecutionApiV1ExecutePostResponses, CreatePodCommandEvent, CreateReplaySessionApiV1ReplaySessionsPostData, CreateReplaySessionApiV1ReplaySessionsPostError, CreateReplaySessionApiV1ReplaySessionsPostErrors, CreateReplaySessionApiV1ReplaySessionsPostResponse, CreateReplaySessionApiV1ReplaySessionsPostResponses, CreateSavedScriptApiV1ScriptsPostData, CreateSavedScriptApiV1ScriptsPostError, CreateSavedScriptApiV1ScriptsPostErrors, CreateSavedScriptApiV1ScriptsPostResponse, CreateSavedScriptApiV1ScriptsPostResponses, CreateUserApiV1AdminUsersPostData, CreateUserApiV1AdminUsersPostError, CreateUserApiV1AdminUsersPostErrors, CreateUserApiV1AdminUsersPostResponse, CreateUserApiV1AdminUsersPostResponses, DeleteEventApiV1AdminEventsEventIdDeleteData, DeleteEventApiV1AdminEventsEventIdDeleteError, DeleteEventApiV1AdminEventsEventIdDeleteErrors, DeleteEventApiV1AdminEventsEventIdDeleteResponse, DeleteEventApiV1AdminEventsEventIdDeleteResponses, DeleteEventApiV1EventsEventIdDeleteData, DeleteEventApiV1EventsEventIdDeleteError, DeleteEventApiV1EventsEventIdDeleteErrors, DeleteEventApiV1EventsEventIdDeleteResponse, DeleteEventApiV1EventsEventIdDeleteResponses, DeleteEventResponse, DeleteExecutionApiV1ExecutionsExecutionIdDeleteData, DeleteExecutionApiV1ExecutionsExecutionIdDeleteError, DeleteExecutionApiV1ExecutionsExecutionIdDeleteErrors, DeleteExecutionApiV1ExecutionsExecutionIdDeleteResponse, DeleteExecutionApiV1ExecutionsExecutionIdDeleteResponses, DeleteNotificationApiV1NotificationsNotificationIdDeleteData, DeleteNotificationApiV1NotificationsNotificationIdDeleteError, DeleteNotificationApiV1NotificationsNotificationIdDeleteErrors, DeleteNotificationApiV1NotificationsNotificationIdDeleteResponse, DeleteNotificationApiV1NotificationsNotificationIdDeleteResponses, DeleteNotificationResponse, DeletePodCommandEvent, DeleteResponse, DeleteSavedScriptApiV1ScriptsScriptIdDeleteData, DeleteSavedScriptApiV1ScriptsScriptIdDeleteError, DeleteSavedScriptApiV1ScriptsScriptIdDeleteErrors, DeleteSavedScriptApiV1ScriptsScriptIdDeleteResponse, DeleteSavedScriptApiV1ScriptsScriptIdDeleteResponses, DeleteUserApiV1AdminUsersUserIdDeleteData, DeleteUserApiV1AdminUsersUserIdDeleteError, DeleteUserApiV1AdminUsersUserIdDeleteErrors, DeleteUserApiV1AdminUsersUserIdDeleteResponse, DeleteUserApiV1AdminUsersUserIdDeleteResponses, DeleteUserResponse, DerivedCounts, DiscardDlqMessageApiV1DlqMessagesEventIdDeleteData, DiscardDlqMessageApiV1DlqMessagesEventIdDeleteError, DiscardDlqMessageApiV1DlqMessagesEventIdDeleteErrors, DiscardDlqMessageApiV1DlqMessagesEventIdDeleteResponse, DiscardDlqMessageApiV1DlqMessagesEventIdDeleteResponses, DlqBatchRetryResponse, DlqMessageDetail, DlqMessageDiscardedEvent, DlqMessageReceivedEvent, DlqMessageResponse, DlqMessageRetriedEvent, DlqMessagesResponse, DlqMessageStatus, DlqRetryResult, DlqTopicSummaryResponse, EditorSettings, EndpointGroup, EndpointUsageStats, Environment, ErrorResponse, EventBrowseRequest, EventBrowseResponse, EventDeleteResponse, EventDetailResponse, EventFilter, EventFilterRequest, EventListResponse, EventMetadata, EventReplayRequest, EventReplayResponse, EventReplayStatusResponse, EventReplayStatusResponseWritable, EventStatistics, EventStatsResponse, EventSummary, EventType, EventTypeCountSchema, ExampleScripts, ExecutionAcceptedEvent, ExecutionCancelledEvent, ExecutionCompletedEvent, ExecutionErrorType, ExecutionEventsApiV1EventsExecutionsExecutionIdGetData, ExecutionEventsApiV1EventsExecutionsExecutionIdGetError, ExecutionEventsApiV1EventsExecutionsExecutionIdGetErrors, ExecutionEventsApiV1EventsExecutionsExecutionIdGetResponse, ExecutionEventsApiV1EventsExecutionsExecutionIdGetResponses, ExecutionFailedEvent, ExecutionLimitsSchema, ExecutionListResponse, ExecutionQueuedEvent, ExecutionRequest, ExecutionRequestedEvent, ExecutionResponse, ExecutionResult, ExecutionRunningEvent, ExecutionStartedEvent, ExecutionStatus, ExecutionTimeoutEvent, ExportEventsCsvApiV1AdminEventsExportCsvGetData, ExportEventsCsvApiV1AdminEventsExportCsvGetError, ExportEventsCsvApiV1AdminEventsExportCsvGetErrors, ExportEventsCsvApiV1AdminEventsExportCsvGetResponses, ExportEventsJsonApiV1AdminEventsExportJsonGetData, ExportEventsJsonApiV1AdminEventsExportJsonGetError, ExportEventsJsonApiV1AdminEventsExportJsonGetErrors, ExportEventsJsonApiV1AdminEventsExportJsonGetResponses, GetCurrentRequestEventsApiV1EventsCurrentRequestGetData, GetCurrentRequestEventsApiV1EventsCurrentRequestGetError, GetCurrentRequestEventsApiV1EventsCurrentRequestGetErrors, GetCurrentRequestEventsApiV1EventsCurrentRequestGetResponse, GetCurrentRequestEventsApiV1EventsCurrentRequestGetResponses, GetCurrentUserProfileApiV1AuthMeGetData, GetCurrentUserProfileApiV1AuthMeGetResponse, GetCurrentUserProfileApiV1AuthMeGetResponses, GetDlqMessageApiV1DlqMessagesEventIdGetData, GetDlqMessageApiV1DlqMessagesEventIdGetError, GetDlqMessageApiV1DlqMessagesEventIdGetErrors, GetDlqMessageApiV1DlqMessagesEventIdGetResponse, GetDlqMessageApiV1DlqMessagesEventIdGetResponses, GetDlqMessagesApiV1DlqMessagesGetData, GetDlqMessagesApiV1DlqMessagesGetError, GetDlqMessagesApiV1DlqMessagesGetErrors, GetDlqMessagesApiV1DlqMessagesGetResponse, GetDlqMessagesApiV1DlqMessagesGetResponses, GetDlqTopicsApiV1DlqTopicsGetData, GetDlqTopicsApiV1DlqTopicsGetResponse, GetDlqTopicsApiV1DlqTopicsGetResponses, GetEventApiV1EventsEventIdGetData, GetEventApiV1EventsEventIdGetError, GetEventApiV1EventsEventIdGetErrors, GetEventApiV1EventsEventIdGetResponse, GetEventApiV1EventsEventIdGetResponses, GetEventDetailApiV1AdminEventsEventIdGetData, GetEventDetailApiV1AdminEventsEventIdGetError, GetEventDetailApiV1AdminEventsEventIdGetErrors, GetEventDetailApiV1AdminEventsEventIdGetResponse, GetEventDetailApiV1AdminEventsEventIdGetResponses, GetEventsByCorrelationApiV1EventsCorrelationCorrelationIdGetData, GetEventsByCorrelationApiV1EventsCorrelationCorrelationIdGetError, GetEventsByCorrelationApiV1EventsCorrelationCorrelationIdGetErrors, GetEventsByCorrelationApiV1EventsCorrelationCorrelationIdGetResponse, GetEventsByCorrelationApiV1EventsCorrelationCorrelationIdGetResponses, GetEventStatisticsApiV1EventsStatisticsGetData, GetEventStatisticsApiV1EventsStatisticsGetError, GetEventStatisticsApiV1EventsStatisticsGetErrors, GetEventStatisticsApiV1EventsStatisticsGetResponse, GetEventStatisticsApiV1EventsStatisticsGetResponses, GetEventStatsApiV1AdminEventsStatsGetData, GetEventStatsApiV1AdminEventsStatsGetError, GetEventStatsApiV1AdminEventsStatsGetErrors, GetEventStatsApiV1AdminEventsStatsGetResponse, GetEventStatsApiV1AdminEventsStatsGetResponses, GetExampleScriptsApiV1ExampleScriptsGetData, GetExampleScriptsApiV1ExampleScriptsGetResponse, GetExampleScriptsApiV1ExampleScriptsGetResponses, GetExecutionEventsApiV1EventsExecutionsExecutionIdEventsGetData, GetExecutionEventsApiV1EventsExecutionsExecutionIdEventsGetError, GetExecutionEventsApiV1EventsExecutionsExecutionIdEventsGetErrors, GetExecutionEventsApiV1EventsExecutionsExecutionIdEventsGetResponse, GetExecutionEventsApiV1EventsExecutionsExecutionIdEventsGetResponses, GetExecutionEventsApiV1ExecutionsExecutionIdEventsGetData, GetExecutionEventsApiV1ExecutionsExecutionIdEventsGetError, GetExecutionEventsApiV1ExecutionsExecutionIdEventsGetErrors, GetExecutionEventsApiV1ExecutionsExecutionIdEventsGetResponse, GetExecutionEventsApiV1ExecutionsExecutionIdEventsGetResponses, GetExecutionSagasApiV1SagasExecutionExecutionIdGetData, GetExecutionSagasApiV1SagasExecutionExecutionIdGetError, GetExecutionSagasApiV1SagasExecutionExecutionIdGetErrors, GetExecutionSagasApiV1SagasExecutionExecutionIdGetResponse, GetExecutionSagasApiV1SagasExecutionExecutionIdGetResponses, GetK8sResourceLimitsApiV1K8sLimitsGetData, GetK8sResourceLimitsApiV1K8sLimitsGetResponse, GetK8sResourceLimitsApiV1K8sLimitsGetResponses, GetNotificationsApiV1NotificationsGetData, GetNotificationsApiV1NotificationsGetError, GetNotificationsApiV1NotificationsGetErrors, GetNotificationsApiV1NotificationsGetResponse, GetNotificationsApiV1NotificationsGetResponses, GetReplaySessionApiV1ReplaySessionsSessionIdGetData, GetReplaySessionApiV1ReplaySessionsSessionIdGetError, GetReplaySessionApiV1ReplaySessionsSessionIdGetErrors, GetReplaySessionApiV1ReplaySessionsSessionIdGetResponse, GetReplaySessionApiV1ReplaySessionsSessionIdGetResponses, GetReplayStatusApiV1AdminEventsReplaySessionIdStatusGetData, GetReplayStatusApiV1AdminEventsReplaySessionIdStatusGetError, GetReplayStatusApiV1AdminEventsReplaySessionIdStatusGetErrors, GetReplayStatusApiV1AdminEventsReplaySessionIdStatusGetResponse, GetReplayStatusApiV1AdminEventsReplaySessionIdStatusGetResponses, GetResultApiV1ExecutionsExecutionIdResultGetData, GetResultApiV1ExecutionsExecutionIdResultGetError, GetResultApiV1ExecutionsExecutionIdResultGetErrors, GetResultApiV1ExecutionsExecutionIdResultGetResponse, GetResultApiV1ExecutionsExecutionIdResultGetResponses, GetSagaStatusApiV1SagasSagaIdGetData, GetSagaStatusApiV1SagasSagaIdGetError, GetSagaStatusApiV1SagasSagaIdGetErrors, GetSagaStatusApiV1SagasSagaIdGetResponse, GetSagaStatusApiV1SagasSagaIdGetResponses, GetSavedScriptApiV1ScriptsScriptIdGetData, GetSavedScriptApiV1ScriptsScriptIdGetError, GetSavedScriptApiV1ScriptsScriptIdGetErrors, GetSavedScriptApiV1ScriptsScriptIdGetResponse, GetSavedScriptApiV1ScriptsScriptIdGetResponses, GetSettingsHistoryApiV1UserSettingsHistoryGetData, GetSettingsHistoryApiV1UserSettingsHistoryGetError, GetSettingsHistoryApiV1UserSettingsHistoryGetErrors, GetSettingsHistoryApiV1UserSettingsHistoryGetResponse, GetSettingsHistoryApiV1UserSettingsHistoryGetResponses, GetSubscriptionsApiV1NotificationsSubscriptionsGetData, GetSubscriptionsApiV1NotificationsSubscriptionsGetResponse, GetSubscriptionsApiV1NotificationsSubscriptionsGetResponses, GetSystemSettingsApiV1AdminSettingsGetData, GetSystemSettingsApiV1AdminSettingsGetError, GetSystemSettingsApiV1AdminSettingsGetErrors, GetSystemSettingsApiV1AdminSettingsGetResponse, GetSystemSettingsApiV1AdminSettingsGetResponses, GetUnreadCountApiV1NotificationsUnreadCountGetData, GetUnreadCountApiV1NotificationsUnreadCountGetResponse, GetUnreadCountApiV1NotificationsUnreadCountGetResponses, GetUserApiV1AdminUsersUserIdGetData, GetUserApiV1AdminUsersUserIdGetError, GetUserApiV1AdminUsersUserIdGetErrors, GetUserApiV1AdminUsersUserIdGetResponse, GetUserApiV1AdminUsersUserIdGetResponses, GetUserEventsApiV1EventsUserGetData, GetUserEventsApiV1EventsUserGetError, GetUserEventsApiV1EventsUserGetErrors, GetUserEventsApiV1EventsUserGetResponse, GetUserEventsApiV1EventsUserGetResponses, GetUserExecutionsApiV1UserExecutionsGetData, GetUserExecutionsApiV1UserExecutionsGetError, GetUserExecutionsApiV1UserExecutionsGetErrors, GetUserExecutionsApiV1UserExecutionsGetResponse, GetUserExecutionsApiV1UserExecutionsGetResponses, GetUserOverviewApiV1AdminUsersUserIdOverviewGetData, GetUserOverviewApiV1AdminUsersUserIdOverviewGetError, GetUserOverviewApiV1AdminUsersUserIdOverviewGetErrors, GetUserOverviewApiV1AdminUsersUserIdOverviewGetResponse, GetUserOverviewApiV1AdminUsersUserIdOverviewGetResponses, GetUserRateLimitsApiV1AdminUsersUserIdRateLimitsGetData, GetUserRateLimitsApiV1AdminUsersUserIdRateLimitsGetError, GetUserRateLimitsApiV1AdminUsersUserIdRateLimitsGetErrors, GetUserRateLimitsApiV1AdminUsersUserIdRateLimitsGetResponse, GetUserRateLimitsApiV1AdminUsersUserIdRateLimitsGetResponses, GetUserSettingsApiV1UserSettingsGetData, GetUserSettingsApiV1UserSettingsGetResponse, GetUserSettingsApiV1UserSettingsGetResponses, HourlyEventCountSchema, HttpValidationError, KafkaTopic, LanguageInfo, ListReplaySessionsApiV1ReplaySessionsGetData, ListReplaySessionsApiV1ReplaySessionsGetError, ListReplaySessionsApiV1ReplaySessionsGetErrors, ListReplaySessionsApiV1ReplaySessionsGetResponse, ListReplaySessionsApiV1ReplaySessionsGetResponses, ListSagasApiV1SagasGetData, ListSagasApiV1SagasGetError, ListSagasApiV1SagasGetErrors, ListSagasApiV1SagasGetResponse, ListSagasApiV1SagasGetResponses, ListSavedScriptsApiV1ScriptsGetData, ListSavedScriptsApiV1ScriptsGetResponse, ListSavedScriptsApiV1ScriptsGetResponses, ListUsersApiV1AdminUsersGetData, ListUsersApiV1AdminUsersGetError, ListUsersApiV1AdminUsersGetErrors, ListUsersApiV1AdminUsersGetResponse, ListUsersApiV1AdminUsersGetResponses, LivenessApiV1HealthLiveGetData, LivenessApiV1HealthLiveGetResponse, LivenessApiV1HealthLiveGetResponses, LivenessResponse, LoginApiV1AuthLoginPostData, LoginApiV1AuthLoginPostError, LoginApiV1AuthLoginPostErrors, LoginApiV1AuthLoginPostResponse, LoginApiV1AuthLoginPostResponses, LoginMethod, LoginResponse, LogoutApiV1AuthLogoutPostData, LogoutApiV1AuthLogoutPostResponse, LogoutApiV1AuthLogoutPostResponses, ManualRetryRequest, MarkAllReadApiV1NotificationsMarkAllReadPostData, MarkAllReadApiV1NotificationsMarkAllReadPostResponse, MarkAllReadApiV1NotificationsMarkAllReadPostResponses, MarkNotificationReadApiV1NotificationsNotificationIdReadPutData, MarkNotificationReadApiV1NotificationsNotificationIdReadPutError, MarkNotificationReadApiV1NotificationsNotificationIdReadPutErrors, MarkNotificationReadApiV1NotificationsNotificationIdReadPutResponse, MarkNotificationReadApiV1NotificationsNotificationIdReadPutResponses, MessageResponse, MonitoringSettingsSchema, NotificationAllReadEvent, NotificationChannel, NotificationClickedEvent, NotificationCreatedEvent, NotificationDeliveredEvent, NotificationFailedEvent, NotificationListResponse, NotificationPreferencesUpdatedEvent, NotificationReadEvent, NotificationResponse, NotificationSentEvent, NotificationSettings, NotificationSeverity, NotificationStatus, NotificationStreamApiV1EventsNotificationsStreamGetData, NotificationStreamApiV1EventsNotificationsStreamGetResponse, NotificationStreamApiV1EventsNotificationsStreamGetResponses, NotificationSubscription, PasswordResetRequest, PauseReplaySessionApiV1ReplaySessionsSessionIdPausePostData, PauseReplaySessionApiV1ReplaySessionsSessionIdPausePostError, PauseReplaySessionApiV1ReplaySessionsSessionIdPausePostErrors, PauseReplaySessionApiV1ReplaySessionsSessionIdPausePostResponse, PauseReplaySessionApiV1ReplaySessionsSessionIdPausePostResponses, PodCreatedEvent, PodDeletedEvent, PodFailedEvent, PodRunningEvent, PodScheduledEvent, PodSucceededEvent, PodTerminatedEvent, PublishCustomEventApiV1EventsPublishPostData, PublishCustomEventApiV1EventsPublishPostError, PublishCustomEventApiV1EventsPublishPostErrors, PublishCustomEventApiV1EventsPublishPostResponse, PublishCustomEventApiV1EventsPublishPostResponses, PublishEventRequest, PublishEventResponse, QueryEventsApiV1EventsQueryPostData, QueryEventsApiV1EventsQueryPostError, QueryEventsApiV1EventsQueryPostErrors, QueryEventsApiV1EventsQueryPostResponse, QueryEventsApiV1EventsQueryPostResponses, QueuePriority, QuotaExceededEvent, RateLimitAlgorithm, RateLimitExceededEvent, RateLimitRuleRequest, RateLimitRuleResponse, RateLimitSummary, RateLimitUpdateRequest, RateLimitUpdateResponse, RegisterApiV1AuthRegisterPostData, RegisterApiV1AuthRegisterPostError, RegisterApiV1AuthRegisterPostErrors, RegisterApiV1AuthRegisterPostResponse, RegisterApiV1AuthRegisterPostResponses, ReleaseResourcesCommandEvent, ReplayAggregateEventsApiV1EventsReplayAggregateIdPostData, ReplayAggregateEventsApiV1EventsReplayAggregateIdPostError, ReplayAggregateEventsApiV1EventsReplayAggregateIdPostErrors, ReplayAggregateEventsApiV1EventsReplayAggregateIdPostResponse, ReplayAggregateEventsApiV1EventsReplayAggregateIdPostResponses, ReplayAggregateResponse, ReplayConfigSchema, ReplayError, ReplayEventsApiV1AdminEventsReplayPostData, ReplayEventsApiV1AdminEventsReplayPostError, ReplayEventsApiV1AdminEventsReplayPostErrors, ReplayEventsApiV1AdminEventsReplayPostResponse, ReplayEventsApiV1AdminEventsReplayPostResponses, ReplayFilter, ReplayFilterSchema, ReplayRequest, ReplayResponse, ReplaySession, ReplayStatus, ReplayTarget, ReplayType, ResetSystemSettingsApiV1AdminSettingsResetPostData, ResetSystemSettingsApiV1AdminSettingsResetPostError, ResetSystemSettingsApiV1AdminSettingsResetPostErrors, ResetSystemSettingsApiV1AdminSettingsResetPostResponse, ResetSystemSettingsApiV1AdminSettingsResetPostResponses, ResetUserPasswordApiV1AdminUsersUserIdResetPasswordPostData, ResetUserPasswordApiV1AdminUsersUserIdResetPasswordPostError, ResetUserPasswordApiV1AdminUsersUserIdResetPasswordPostErrors, ResetUserPasswordApiV1AdminUsersUserIdResetPasswordPostResponse, ResetUserPasswordApiV1AdminUsersUserIdResetPasswordPostResponses, ResetUserRateLimitsApiV1AdminUsersUserIdRateLimitsResetPostData, ResetUserRateLimitsApiV1AdminUsersUserIdRateLimitsResetPostError, ResetUserRateLimitsApiV1AdminUsersUserIdRateLimitsResetPostErrors, ResetUserRateLimitsApiV1AdminUsersUserIdRateLimitsResetPostResponse, ResetUserRateLimitsApiV1AdminUsersUserIdRateLimitsResetPostResponses, ResourceLimitExceededEvent, ResourceLimits, ResourceUsage, ResourceUsageDomain, RestoreSettingsApiV1UserSettingsRestorePostData, RestoreSettingsApiV1UserSettingsRestorePostError, RestoreSettingsApiV1UserSettingsRestorePostErrors, RestoreSettingsApiV1UserSettingsRestorePostResponse, RestoreSettingsApiV1UserSettingsRestorePostResponses, RestoreSettingsRequest, ResultFailedEvent, ResultStoredEvent, ResumeReplaySessionApiV1ReplaySessionsSessionIdResumePostData, ResumeReplaySessionApiV1ReplaySessionsSessionIdResumePostError, ResumeReplaySessionApiV1ReplaySessionsSessionIdResumePostErrors, ResumeReplaySessionApiV1ReplaySessionsSessionIdResumePostResponse, ResumeReplaySessionApiV1ReplaySessionsSessionIdResumePostResponses, RetryDlqMessagesApiV1DlqRetryPostData, RetryDlqMessagesApiV1DlqRetryPostError, RetryDlqMessagesApiV1DlqRetryPostErrors, RetryDlqMessagesApiV1DlqRetryPostResponse, RetryDlqMessagesApiV1DlqRetryPostResponses, RetryExecutionApiV1ExecutionsExecutionIdRetryPostData, RetryExecutionApiV1ExecutionsExecutionIdRetryPostError, RetryExecutionApiV1ExecutionsExecutionIdRetryPostErrors, RetryExecutionApiV1ExecutionsExecutionIdRetryPostResponse, RetryExecutionApiV1ExecutionsExecutionIdRetryPostResponses, RetryExecutionRequest, RetryPolicyRequest, RetryStrategy, SagaCancellationResponse, SagaCancelledEvent, SagaCompensatedEvent, SagaCompensatingEvent, SagaCompletedEvent, SagaFailedEvent, SagaListResponse, SagaStartedEvent, SagaState, SagaStatusResponse, SavedScriptCreateRequest, SavedScriptResponse, SavedScriptUpdate, ScriptDeletedEvent, ScriptSavedEvent, ScriptSharedEvent, SecuritySettingsSchema, SecurityViolationEvent, ServiceEventCountSchema, ServiceRecoveredEvent, ServiceUnhealthyEvent, SessionConfigSummary, SessionSummary, SessionSummaryWritable, SetRetryPolicyApiV1DlqRetryPolicyPostData, SetRetryPolicyApiV1DlqRetryPolicyPostError, SetRetryPolicyApiV1DlqRetryPolicyPostErrors, SetRetryPolicyApiV1DlqRetryPolicyPostResponse, SetRetryPolicyApiV1DlqRetryPolicyPostResponses, SettingsHistoryEntry, SettingsHistoryResponse, SortOrder, SseControlEvent, SseExecutionEventData, StartReplaySessionApiV1ReplaySessionsSessionIdStartPostData, StartReplaySessionApiV1ReplaySessionsSessionIdStartPostError, StartReplaySessionApiV1ReplaySessionsSessionIdStartPostErrors, StartReplaySessionApiV1ReplaySessionsSessionIdStartPostResponse, StartReplaySessionApiV1ReplaySessionsSessionIdStartPostResponses, StorageType, SubscriptionsResponse, SubscriptionUpdate, SystemErrorEvent, SystemSettings, Theme, ThemeUpdateRequest, UnreadCountResponse, UpdateCustomSettingApiV1UserSettingsCustomKeyPutData, UpdateCustomSettingApiV1UserSettingsCustomKeyPutError, UpdateCustomSettingApiV1UserSettingsCustomKeyPutErrors, UpdateCustomSettingApiV1UserSettingsCustomKeyPutResponse, UpdateCustomSettingApiV1UserSettingsCustomKeyPutResponses, UpdateEditorSettingsApiV1UserSettingsEditorPutData, UpdateEditorSettingsApiV1UserSettingsEditorPutError, UpdateEditorSettingsApiV1UserSettingsEditorPutErrors, UpdateEditorSettingsApiV1UserSettingsEditorPutResponse, UpdateEditorSettingsApiV1UserSettingsEditorPutResponses, UpdateNotificationSettingsApiV1UserSettingsNotificationsPutData, UpdateNotificationSettingsApiV1UserSettingsNotificationsPutError, UpdateNotificationSettingsApiV1UserSettingsNotificationsPutErrors, UpdateNotificationSettingsApiV1UserSettingsNotificationsPutResponse, UpdateNotificationSettingsApiV1UserSettingsNotificationsPutResponses, UpdateSavedScriptApiV1ScriptsScriptIdPutData, UpdateSavedScriptApiV1ScriptsScriptIdPutError, UpdateSavedScriptApiV1ScriptsScriptIdPutErrors, UpdateSavedScriptApiV1ScriptsScriptIdPutResponse, UpdateSavedScriptApiV1ScriptsScriptIdPutResponses, UpdateSubscriptionApiV1NotificationsSubscriptionsChannelPutData, UpdateSubscriptionApiV1NotificationsSubscriptionsChannelPutError, UpdateSubscriptionApiV1NotificationsSubscriptionsChannelPutErrors, UpdateSubscriptionApiV1NotificationsSubscriptionsChannelPutResponse, UpdateSubscriptionApiV1NotificationsSubscriptionsChannelPutResponses, UpdateSystemSettingsApiV1AdminSettingsPutData, UpdateSystemSettingsApiV1AdminSettingsPutError, UpdateSystemSettingsApiV1AdminSettingsPutErrors, UpdateSystemSettingsApiV1AdminSettingsPutResponse, UpdateSystemSettingsApiV1AdminSettingsPutResponses, UpdateThemeApiV1UserSettingsThemePutData, UpdateThemeApiV1UserSettingsThemePutError, UpdateThemeApiV1UserSettingsThemePutErrors, UpdateThemeApiV1UserSettingsThemePutResponse, UpdateThemeApiV1UserSettingsThemePutResponses, UpdateUserApiV1AdminUsersUserIdPutData, UpdateUserApiV1AdminUsersUserIdPutError, UpdateUserApiV1AdminUsersUserIdPutErrors, UpdateUserApiV1AdminUsersUserIdPutResponse, UpdateUserApiV1AdminUsersUserIdPutResponses, UpdateUserRateLimitsApiV1AdminUsersUserIdRateLimitsPutData, UpdateUserRateLimitsApiV1AdminUsersUserIdRateLimitsPutError, UpdateUserRateLimitsApiV1AdminUsersUserIdRateLimitsPutErrors, UpdateUserRateLimitsApiV1AdminUsersUserIdRateLimitsPutResponse, UpdateUserRateLimitsApiV1AdminUsersUserIdRateLimitsPutResponses, UpdateUserSettingsApiV1UserSettingsPutData, UpdateUserSettingsApiV1UserSettingsPutError, UpdateUserSettingsApiV1UserSettingsPutErrors, UpdateUserSettingsApiV1UserSettingsPutResponse, UpdateUserSettingsApiV1UserSettingsPutResponses, UserCreate, UserDeletedEvent, UserEventCountSchema, UserListResponse, UserLoggedInEvent, UserLoggedOutEvent, UserLoginEvent, UserRateLimitConfigResponse, UserRateLimitsResponse, UserRegisteredEvent, UserResponse, UserRole, UserSettings, UserSettingsUpdate, UserSettingsUpdatedEvent, UserUpdate, UserUpdatedEvent, ValidationError } from './types.gen';
diff --git a/frontend/src/lib/api/sdk.gen.ts b/frontend/src/lib/api/sdk.gen.ts
index bbfa8740..62bcab28 100644
--- a/frontend/src/lib/api/sdk.gen.ts
+++ b/frontend/src/lib/api/sdk.gen.ts
@@ -2,7 +2,7 @@
import { type Client, type Options as Options2, type TDataShape, urlSearchParamsBodySerializer } from './client';
import { client } from './client.gen';
-import type { AggregateEventsApiV1EventsAggregatePostData, AggregateEventsApiV1EventsAggregatePostErrors, AggregateEventsApiV1EventsAggregatePostResponses, BrowseEventsApiV1AdminEventsBrowsePostData, BrowseEventsApiV1AdminEventsBrowsePostErrors, BrowseEventsApiV1AdminEventsBrowsePostResponses, CancelExecutionApiV1ExecutionsExecutionIdCancelPostData, CancelExecutionApiV1ExecutionsExecutionIdCancelPostErrors, CancelExecutionApiV1ExecutionsExecutionIdCancelPostResponses, CancelReplaySessionApiV1ReplaySessionsSessionIdCancelPostData, CancelReplaySessionApiV1ReplaySessionsSessionIdCancelPostErrors, CancelReplaySessionApiV1ReplaySessionsSessionIdCancelPostResponses, CancelSagaApiV1SagasSagaIdCancelPostData, CancelSagaApiV1SagasSagaIdCancelPostErrors, CancelSagaApiV1SagasSagaIdCancelPostResponses, CleanupOldSessionsApiV1ReplayCleanupPostData, CleanupOldSessionsApiV1ReplayCleanupPostErrors, CleanupOldSessionsApiV1ReplayCleanupPostResponses, CreateExecutionApiV1ExecutePostData, CreateExecutionApiV1ExecutePostErrors, CreateExecutionApiV1ExecutePostResponses, CreateReplaySessionApiV1ReplaySessionsPostData, CreateReplaySessionApiV1ReplaySessionsPostErrors, CreateReplaySessionApiV1ReplaySessionsPostResponses, CreateSavedScriptApiV1ScriptsPostData, CreateSavedScriptApiV1ScriptsPostErrors, CreateSavedScriptApiV1ScriptsPostResponses, CreateUserApiV1AdminUsersPostData, CreateUserApiV1AdminUsersPostErrors, CreateUserApiV1AdminUsersPostResponses, DeleteEventApiV1AdminEventsEventIdDeleteData, DeleteEventApiV1AdminEventsEventIdDeleteErrors, DeleteEventApiV1AdminEventsEventIdDeleteResponses, DeleteEventApiV1EventsEventIdDeleteData, DeleteEventApiV1EventsEventIdDeleteErrors, DeleteEventApiV1EventsEventIdDeleteResponses, DeleteExecutionApiV1ExecutionsExecutionIdDeleteData, DeleteExecutionApiV1ExecutionsExecutionIdDeleteErrors, DeleteExecutionApiV1ExecutionsExecutionIdDeleteResponses, DeleteNotificationApiV1NotificationsNotificationIdDeleteData, DeleteNotificationApiV1NotificationsNotificationIdDeleteErrors, DeleteNotificationApiV1NotificationsNotificationIdDeleteResponses, DeleteSavedScriptApiV1ScriptsScriptIdDeleteData, DeleteSavedScriptApiV1ScriptsScriptIdDeleteErrors, DeleteSavedScriptApiV1ScriptsScriptIdDeleteResponses, DeleteUserApiV1AdminUsersUserIdDeleteData, DeleteUserApiV1AdminUsersUserIdDeleteErrors, DeleteUserApiV1AdminUsersUserIdDeleteResponses, DiscardDlqMessageApiV1DlqMessagesEventIdDeleteData, DiscardDlqMessageApiV1DlqMessagesEventIdDeleteErrors, DiscardDlqMessageApiV1DlqMessagesEventIdDeleteResponses, ExecutionEventsApiV1EventsExecutionsExecutionIdGetData, ExecutionEventsApiV1EventsExecutionsExecutionIdGetErrors, ExecutionEventsApiV1EventsExecutionsExecutionIdGetResponses, ExportEventsCsvApiV1AdminEventsExportCsvGetData, ExportEventsCsvApiV1AdminEventsExportCsvGetErrors, ExportEventsCsvApiV1AdminEventsExportCsvGetResponses, ExportEventsJsonApiV1AdminEventsExportJsonGetData, ExportEventsJsonApiV1AdminEventsExportJsonGetErrors, ExportEventsJsonApiV1AdminEventsExportJsonGetResponses, GetCurrentRequestEventsApiV1EventsCurrentRequestGetData, GetCurrentRequestEventsApiV1EventsCurrentRequestGetErrors, GetCurrentRequestEventsApiV1EventsCurrentRequestGetResponses, GetCurrentUserProfileApiV1AuthMeGetData, GetCurrentUserProfileApiV1AuthMeGetResponses, GetDlqMessageApiV1DlqMessagesEventIdGetData, GetDlqMessageApiV1DlqMessagesEventIdGetErrors, GetDlqMessageApiV1DlqMessagesEventIdGetResponses, GetDlqMessagesApiV1DlqMessagesGetData, GetDlqMessagesApiV1DlqMessagesGetErrors, GetDlqMessagesApiV1DlqMessagesGetResponses, GetDlqStatisticsApiV1DlqStatsGetData, GetDlqStatisticsApiV1DlqStatsGetResponses, GetDlqTopicsApiV1DlqTopicsGetData, GetDlqTopicsApiV1DlqTopicsGetResponses, GetEventApiV1EventsEventIdGetData, GetEventApiV1EventsEventIdGetErrors, GetEventApiV1EventsEventIdGetResponses, GetEventDetailApiV1AdminEventsEventIdGetData, GetEventDetailApiV1AdminEventsEventIdGetErrors, GetEventDetailApiV1AdminEventsEventIdGetResponses, GetEventsByCorrelationApiV1EventsCorrelationCorrelationIdGetData, GetEventsByCorrelationApiV1EventsCorrelationCorrelationIdGetErrors, GetEventsByCorrelationApiV1EventsCorrelationCorrelationIdGetResponses, GetEventStatisticsApiV1EventsStatisticsGetData, GetEventStatisticsApiV1EventsStatisticsGetErrors, GetEventStatisticsApiV1EventsStatisticsGetResponses, GetEventStatsApiV1AdminEventsStatsGetData, GetEventStatsApiV1AdminEventsStatsGetErrors, GetEventStatsApiV1AdminEventsStatsGetResponses, GetExampleScriptsApiV1ExampleScriptsGetData, GetExampleScriptsApiV1ExampleScriptsGetResponses, GetExecutionEventsApiV1EventsExecutionsExecutionIdEventsGetData, GetExecutionEventsApiV1EventsExecutionsExecutionIdEventsGetErrors, GetExecutionEventsApiV1EventsExecutionsExecutionIdEventsGetResponses, GetExecutionEventsApiV1ExecutionsExecutionIdEventsGetData, GetExecutionEventsApiV1ExecutionsExecutionIdEventsGetErrors, GetExecutionEventsApiV1ExecutionsExecutionIdEventsGetResponses, GetExecutionSagasApiV1SagasExecutionExecutionIdGetData, GetExecutionSagasApiV1SagasExecutionExecutionIdGetErrors, GetExecutionSagasApiV1SagasExecutionExecutionIdGetResponses, GetK8sResourceLimitsApiV1K8sLimitsGetData, GetK8sResourceLimitsApiV1K8sLimitsGetResponses, GetNotificationsApiV1NotificationsGetData, GetNotificationsApiV1NotificationsGetErrors, GetNotificationsApiV1NotificationsGetResponses, GetReplaySessionApiV1ReplaySessionsSessionIdGetData, GetReplaySessionApiV1ReplaySessionsSessionIdGetErrors, GetReplaySessionApiV1ReplaySessionsSessionIdGetResponses, GetReplayStatusApiV1AdminEventsReplaySessionIdStatusGetData, GetReplayStatusApiV1AdminEventsReplaySessionIdStatusGetErrors, GetReplayStatusApiV1AdminEventsReplaySessionIdStatusGetResponses, GetResultApiV1ExecutionsExecutionIdResultGetData, GetResultApiV1ExecutionsExecutionIdResultGetErrors, GetResultApiV1ExecutionsExecutionIdResultGetResponses, GetSagaStatusApiV1SagasSagaIdGetData, GetSagaStatusApiV1SagasSagaIdGetErrors, GetSagaStatusApiV1SagasSagaIdGetResponses, GetSavedScriptApiV1ScriptsScriptIdGetData, GetSavedScriptApiV1ScriptsScriptIdGetErrors, GetSavedScriptApiV1ScriptsScriptIdGetResponses, GetSettingsHistoryApiV1UserSettingsHistoryGetData, GetSettingsHistoryApiV1UserSettingsHistoryGetErrors, GetSettingsHistoryApiV1UserSettingsHistoryGetResponses, GetSubscriptionsApiV1NotificationsSubscriptionsGetData, GetSubscriptionsApiV1NotificationsSubscriptionsGetResponses, GetSystemSettingsApiV1AdminSettingsGetData, GetSystemSettingsApiV1AdminSettingsGetErrors, GetSystemSettingsApiV1AdminSettingsGetResponses, GetUnreadCountApiV1NotificationsUnreadCountGetData, GetUnreadCountApiV1NotificationsUnreadCountGetResponses, GetUserApiV1AdminUsersUserIdGetData, GetUserApiV1AdminUsersUserIdGetErrors, GetUserApiV1AdminUsersUserIdGetResponses, GetUserEventsApiV1EventsUserGetData, GetUserEventsApiV1EventsUserGetErrors, GetUserEventsApiV1EventsUserGetResponses, GetUserExecutionsApiV1UserExecutionsGetData, GetUserExecutionsApiV1UserExecutionsGetErrors, GetUserExecutionsApiV1UserExecutionsGetResponses, GetUserOverviewApiV1AdminUsersUserIdOverviewGetData, GetUserOverviewApiV1AdminUsersUserIdOverviewGetErrors, GetUserOverviewApiV1AdminUsersUserIdOverviewGetResponses, GetUserRateLimitsApiV1AdminUsersUserIdRateLimitsGetData, GetUserRateLimitsApiV1AdminUsersUserIdRateLimitsGetErrors, GetUserRateLimitsApiV1AdminUsersUserIdRateLimitsGetResponses, GetUserSettingsApiV1UserSettingsGetData, GetUserSettingsApiV1UserSettingsGetResponses, ListEventTypesApiV1EventsTypesListGetData, ListEventTypesApiV1EventsTypesListGetResponses, ListReplaySessionsApiV1ReplaySessionsGetData, ListReplaySessionsApiV1ReplaySessionsGetErrors, ListReplaySessionsApiV1ReplaySessionsGetResponses, ListSagasApiV1SagasGetData, ListSagasApiV1SagasGetErrors, ListSagasApiV1SagasGetResponses, ListSavedScriptsApiV1ScriptsGetData, ListSavedScriptsApiV1ScriptsGetResponses, ListUsersApiV1AdminUsersGetData, ListUsersApiV1AdminUsersGetErrors, ListUsersApiV1AdminUsersGetResponses, LivenessApiV1HealthLiveGetData, LivenessApiV1HealthLiveGetResponses, LoginApiV1AuthLoginPostData, LoginApiV1AuthLoginPostErrors, LoginApiV1AuthLoginPostResponses, LogoutApiV1AuthLogoutPostData, LogoutApiV1AuthLogoutPostResponses, MarkAllReadApiV1NotificationsMarkAllReadPostData, MarkAllReadApiV1NotificationsMarkAllReadPostResponses, MarkNotificationReadApiV1NotificationsNotificationIdReadPutData, MarkNotificationReadApiV1NotificationsNotificationIdReadPutErrors, MarkNotificationReadApiV1NotificationsNotificationIdReadPutResponses, NotificationStreamApiV1EventsNotificationsStreamGetData, NotificationStreamApiV1EventsNotificationsStreamGetResponses, PauseReplaySessionApiV1ReplaySessionsSessionIdPausePostData, PauseReplaySessionApiV1ReplaySessionsSessionIdPausePostErrors, PauseReplaySessionApiV1ReplaySessionsSessionIdPausePostResponses, PublishCustomEventApiV1EventsPublishPostData, PublishCustomEventApiV1EventsPublishPostErrors, PublishCustomEventApiV1EventsPublishPostResponses, QueryEventsApiV1EventsQueryPostData, QueryEventsApiV1EventsQueryPostErrors, QueryEventsApiV1EventsQueryPostResponses, ReadinessApiV1HealthReadyGetData, ReadinessApiV1HealthReadyGetResponses, ReceiveGrafanaAlertsApiV1AlertsGrafanaPostData, ReceiveGrafanaAlertsApiV1AlertsGrafanaPostErrors, ReceiveGrafanaAlertsApiV1AlertsGrafanaPostResponses, RegisterApiV1AuthRegisterPostData, RegisterApiV1AuthRegisterPostErrors, RegisterApiV1AuthRegisterPostResponses, ReplayAggregateEventsApiV1EventsReplayAggregateIdPostData, ReplayAggregateEventsApiV1EventsReplayAggregateIdPostErrors, ReplayAggregateEventsApiV1EventsReplayAggregateIdPostResponses, ReplayEventsApiV1AdminEventsReplayPostData, ReplayEventsApiV1AdminEventsReplayPostErrors, ReplayEventsApiV1AdminEventsReplayPostResponses, ResetSystemSettingsApiV1AdminSettingsResetPostData, ResetSystemSettingsApiV1AdminSettingsResetPostErrors, ResetSystemSettingsApiV1AdminSettingsResetPostResponses, ResetUserPasswordApiV1AdminUsersUserIdResetPasswordPostData, ResetUserPasswordApiV1AdminUsersUserIdResetPasswordPostErrors, ResetUserPasswordApiV1AdminUsersUserIdResetPasswordPostResponses, ResetUserRateLimitsApiV1AdminUsersUserIdRateLimitsResetPostData, ResetUserRateLimitsApiV1AdminUsersUserIdRateLimitsResetPostErrors, ResetUserRateLimitsApiV1AdminUsersUserIdRateLimitsResetPostResponses, RestoreSettingsApiV1UserSettingsRestorePostData, RestoreSettingsApiV1UserSettingsRestorePostErrors, RestoreSettingsApiV1UserSettingsRestorePostResponses, ResumeReplaySessionApiV1ReplaySessionsSessionIdResumePostData, ResumeReplaySessionApiV1ReplaySessionsSessionIdResumePostErrors, ResumeReplaySessionApiV1ReplaySessionsSessionIdResumePostResponses, RetryDlqMessagesApiV1DlqRetryPostData, RetryDlqMessagesApiV1DlqRetryPostErrors, RetryDlqMessagesApiV1DlqRetryPostResponses, RetryExecutionApiV1ExecutionsExecutionIdRetryPostData, RetryExecutionApiV1ExecutionsExecutionIdRetryPostErrors, RetryExecutionApiV1ExecutionsExecutionIdRetryPostResponses, SetRetryPolicyApiV1DlqRetryPolicyPostData, SetRetryPolicyApiV1DlqRetryPolicyPostErrors, SetRetryPolicyApiV1DlqRetryPolicyPostResponses, StartReplaySessionApiV1ReplaySessionsSessionIdStartPostData, StartReplaySessionApiV1ReplaySessionsSessionIdStartPostErrors, StartReplaySessionApiV1ReplaySessionsSessionIdStartPostResponses, TestGrafanaAlertEndpointApiV1AlertsGrafanaTestGetData, TestGrafanaAlertEndpointApiV1AlertsGrafanaTestGetResponses, UpdateCustomSettingApiV1UserSettingsCustomKeyPutData, UpdateCustomSettingApiV1UserSettingsCustomKeyPutErrors, UpdateCustomSettingApiV1UserSettingsCustomKeyPutResponses, UpdateEditorSettingsApiV1UserSettingsEditorPutData, UpdateEditorSettingsApiV1UserSettingsEditorPutErrors, UpdateEditorSettingsApiV1UserSettingsEditorPutResponses, UpdateNotificationSettingsApiV1UserSettingsNotificationsPutData, UpdateNotificationSettingsApiV1UserSettingsNotificationsPutErrors, UpdateNotificationSettingsApiV1UserSettingsNotificationsPutResponses, UpdateSavedScriptApiV1ScriptsScriptIdPutData, UpdateSavedScriptApiV1ScriptsScriptIdPutErrors, UpdateSavedScriptApiV1ScriptsScriptIdPutResponses, UpdateSubscriptionApiV1NotificationsSubscriptionsChannelPutData, UpdateSubscriptionApiV1NotificationsSubscriptionsChannelPutErrors, UpdateSubscriptionApiV1NotificationsSubscriptionsChannelPutResponses, UpdateSystemSettingsApiV1AdminSettingsPutData, UpdateSystemSettingsApiV1AdminSettingsPutErrors, UpdateSystemSettingsApiV1AdminSettingsPutResponses, UpdateThemeApiV1UserSettingsThemePutData, UpdateThemeApiV1UserSettingsThemePutErrors, UpdateThemeApiV1UserSettingsThemePutResponses, UpdateUserApiV1AdminUsersUserIdPutData, UpdateUserApiV1AdminUsersUserIdPutErrors, UpdateUserApiV1AdminUsersUserIdPutResponses, UpdateUserRateLimitsApiV1AdminUsersUserIdRateLimitsPutData, UpdateUserRateLimitsApiV1AdminUsersUserIdRateLimitsPutErrors, UpdateUserRateLimitsApiV1AdminUsersUserIdRateLimitsPutResponses, UpdateUserSettingsApiV1UserSettingsPutData, UpdateUserSettingsApiV1UserSettingsPutErrors, UpdateUserSettingsApiV1UserSettingsPutResponses, VerifyTokenApiV1AuthVerifyTokenGetData, VerifyTokenApiV1AuthVerifyTokenGetErrors, VerifyTokenApiV1AuthVerifyTokenGetResponses } from './types.gen';
+import type { BrowseEventsApiV1AdminEventsBrowsePostData, BrowseEventsApiV1AdminEventsBrowsePostErrors, BrowseEventsApiV1AdminEventsBrowsePostResponses, CancelExecutionApiV1ExecutionsExecutionIdCancelPostData, CancelExecutionApiV1ExecutionsExecutionIdCancelPostErrors, CancelExecutionApiV1ExecutionsExecutionIdCancelPostResponses, CancelReplaySessionApiV1ReplaySessionsSessionIdCancelPostData, CancelReplaySessionApiV1ReplaySessionsSessionIdCancelPostErrors, CancelReplaySessionApiV1ReplaySessionsSessionIdCancelPostResponses, CancelSagaApiV1SagasSagaIdCancelPostData, CancelSagaApiV1SagasSagaIdCancelPostErrors, CancelSagaApiV1SagasSagaIdCancelPostResponses, CleanupOldSessionsApiV1ReplayCleanupPostData, CleanupOldSessionsApiV1ReplayCleanupPostErrors, CleanupOldSessionsApiV1ReplayCleanupPostResponses, CreateExecutionApiV1ExecutePostData, CreateExecutionApiV1ExecutePostErrors, CreateExecutionApiV1ExecutePostResponses, CreateReplaySessionApiV1ReplaySessionsPostData, CreateReplaySessionApiV1ReplaySessionsPostErrors, CreateReplaySessionApiV1ReplaySessionsPostResponses, CreateSavedScriptApiV1ScriptsPostData, CreateSavedScriptApiV1ScriptsPostErrors, CreateSavedScriptApiV1ScriptsPostResponses, CreateUserApiV1AdminUsersPostData, CreateUserApiV1AdminUsersPostErrors, CreateUserApiV1AdminUsersPostResponses, DeleteEventApiV1AdminEventsEventIdDeleteData, DeleteEventApiV1AdminEventsEventIdDeleteErrors, DeleteEventApiV1AdminEventsEventIdDeleteResponses, DeleteEventApiV1EventsEventIdDeleteData, DeleteEventApiV1EventsEventIdDeleteErrors, DeleteEventApiV1EventsEventIdDeleteResponses, DeleteExecutionApiV1ExecutionsExecutionIdDeleteData, DeleteExecutionApiV1ExecutionsExecutionIdDeleteErrors, DeleteExecutionApiV1ExecutionsExecutionIdDeleteResponses, DeleteNotificationApiV1NotificationsNotificationIdDeleteData, DeleteNotificationApiV1NotificationsNotificationIdDeleteErrors, DeleteNotificationApiV1NotificationsNotificationIdDeleteResponses, DeleteSavedScriptApiV1ScriptsScriptIdDeleteData, DeleteSavedScriptApiV1ScriptsScriptIdDeleteErrors, DeleteSavedScriptApiV1ScriptsScriptIdDeleteResponses, DeleteUserApiV1AdminUsersUserIdDeleteData, DeleteUserApiV1AdminUsersUserIdDeleteErrors, DeleteUserApiV1AdminUsersUserIdDeleteResponses, DiscardDlqMessageApiV1DlqMessagesEventIdDeleteData, DiscardDlqMessageApiV1DlqMessagesEventIdDeleteErrors, DiscardDlqMessageApiV1DlqMessagesEventIdDeleteResponses, ExecutionEventsApiV1EventsExecutionsExecutionIdGetData, ExecutionEventsApiV1EventsExecutionsExecutionIdGetErrors, ExecutionEventsApiV1EventsExecutionsExecutionIdGetResponses, ExportEventsCsvApiV1AdminEventsExportCsvGetData, ExportEventsCsvApiV1AdminEventsExportCsvGetErrors, ExportEventsCsvApiV1AdminEventsExportCsvGetResponses, ExportEventsJsonApiV1AdminEventsExportJsonGetData, ExportEventsJsonApiV1AdminEventsExportJsonGetErrors, ExportEventsJsonApiV1AdminEventsExportJsonGetResponses, GetCurrentRequestEventsApiV1EventsCurrentRequestGetData, GetCurrentRequestEventsApiV1EventsCurrentRequestGetErrors, GetCurrentRequestEventsApiV1EventsCurrentRequestGetResponses, GetCurrentUserProfileApiV1AuthMeGetData, GetCurrentUserProfileApiV1AuthMeGetResponses, GetDlqMessageApiV1DlqMessagesEventIdGetData, GetDlqMessageApiV1DlqMessagesEventIdGetErrors, GetDlqMessageApiV1DlqMessagesEventIdGetResponses, GetDlqMessagesApiV1DlqMessagesGetData, GetDlqMessagesApiV1DlqMessagesGetErrors, GetDlqMessagesApiV1DlqMessagesGetResponses, GetDlqTopicsApiV1DlqTopicsGetData, GetDlqTopicsApiV1DlqTopicsGetResponses, GetEventApiV1EventsEventIdGetData, GetEventApiV1EventsEventIdGetErrors, GetEventApiV1EventsEventIdGetResponses, GetEventDetailApiV1AdminEventsEventIdGetData, GetEventDetailApiV1AdminEventsEventIdGetErrors, GetEventDetailApiV1AdminEventsEventIdGetResponses, GetEventsByCorrelationApiV1EventsCorrelationCorrelationIdGetData, GetEventsByCorrelationApiV1EventsCorrelationCorrelationIdGetErrors, GetEventsByCorrelationApiV1EventsCorrelationCorrelationIdGetResponses, GetEventStatisticsApiV1EventsStatisticsGetData, GetEventStatisticsApiV1EventsStatisticsGetErrors, GetEventStatisticsApiV1EventsStatisticsGetResponses, GetEventStatsApiV1AdminEventsStatsGetData, GetEventStatsApiV1AdminEventsStatsGetErrors, GetEventStatsApiV1AdminEventsStatsGetResponses, GetExampleScriptsApiV1ExampleScriptsGetData, GetExampleScriptsApiV1ExampleScriptsGetResponses, GetExecutionEventsApiV1EventsExecutionsExecutionIdEventsGetData, GetExecutionEventsApiV1EventsExecutionsExecutionIdEventsGetErrors, GetExecutionEventsApiV1EventsExecutionsExecutionIdEventsGetResponses, GetExecutionEventsApiV1ExecutionsExecutionIdEventsGetData, GetExecutionEventsApiV1ExecutionsExecutionIdEventsGetErrors, GetExecutionEventsApiV1ExecutionsExecutionIdEventsGetResponses, GetExecutionSagasApiV1SagasExecutionExecutionIdGetData, GetExecutionSagasApiV1SagasExecutionExecutionIdGetErrors, GetExecutionSagasApiV1SagasExecutionExecutionIdGetResponses, GetK8sResourceLimitsApiV1K8sLimitsGetData, GetK8sResourceLimitsApiV1K8sLimitsGetResponses, GetNotificationsApiV1NotificationsGetData, GetNotificationsApiV1NotificationsGetErrors, GetNotificationsApiV1NotificationsGetResponses, GetReplaySessionApiV1ReplaySessionsSessionIdGetData, GetReplaySessionApiV1ReplaySessionsSessionIdGetErrors, GetReplaySessionApiV1ReplaySessionsSessionIdGetResponses, GetReplayStatusApiV1AdminEventsReplaySessionIdStatusGetData, GetReplayStatusApiV1AdminEventsReplaySessionIdStatusGetErrors, GetReplayStatusApiV1AdminEventsReplaySessionIdStatusGetResponses, GetResultApiV1ExecutionsExecutionIdResultGetData, GetResultApiV1ExecutionsExecutionIdResultGetErrors, GetResultApiV1ExecutionsExecutionIdResultGetResponses, GetSagaStatusApiV1SagasSagaIdGetData, GetSagaStatusApiV1SagasSagaIdGetErrors, GetSagaStatusApiV1SagasSagaIdGetResponses, GetSavedScriptApiV1ScriptsScriptIdGetData, GetSavedScriptApiV1ScriptsScriptIdGetErrors, GetSavedScriptApiV1ScriptsScriptIdGetResponses, GetSettingsHistoryApiV1UserSettingsHistoryGetData, GetSettingsHistoryApiV1UserSettingsHistoryGetErrors, GetSettingsHistoryApiV1UserSettingsHistoryGetResponses, GetSubscriptionsApiV1NotificationsSubscriptionsGetData, GetSubscriptionsApiV1NotificationsSubscriptionsGetResponses, GetSystemSettingsApiV1AdminSettingsGetData, GetSystemSettingsApiV1AdminSettingsGetErrors, GetSystemSettingsApiV1AdminSettingsGetResponses, GetUnreadCountApiV1NotificationsUnreadCountGetData, GetUnreadCountApiV1NotificationsUnreadCountGetResponses, GetUserApiV1AdminUsersUserIdGetData, GetUserApiV1AdminUsersUserIdGetErrors, GetUserApiV1AdminUsersUserIdGetResponses, GetUserEventsApiV1EventsUserGetData, GetUserEventsApiV1EventsUserGetErrors, GetUserEventsApiV1EventsUserGetResponses, GetUserExecutionsApiV1UserExecutionsGetData, GetUserExecutionsApiV1UserExecutionsGetErrors, GetUserExecutionsApiV1UserExecutionsGetResponses, GetUserOverviewApiV1AdminUsersUserIdOverviewGetData, GetUserOverviewApiV1AdminUsersUserIdOverviewGetErrors, GetUserOverviewApiV1AdminUsersUserIdOverviewGetResponses, GetUserRateLimitsApiV1AdminUsersUserIdRateLimitsGetData, GetUserRateLimitsApiV1AdminUsersUserIdRateLimitsGetErrors, GetUserRateLimitsApiV1AdminUsersUserIdRateLimitsGetResponses, GetUserSettingsApiV1UserSettingsGetData, GetUserSettingsApiV1UserSettingsGetResponses, ListReplaySessionsApiV1ReplaySessionsGetData, ListReplaySessionsApiV1ReplaySessionsGetErrors, ListReplaySessionsApiV1ReplaySessionsGetResponses, ListSagasApiV1SagasGetData, ListSagasApiV1SagasGetErrors, ListSagasApiV1SagasGetResponses, ListSavedScriptsApiV1ScriptsGetData, ListSavedScriptsApiV1ScriptsGetResponses, ListUsersApiV1AdminUsersGetData, ListUsersApiV1AdminUsersGetErrors, ListUsersApiV1AdminUsersGetResponses, LivenessApiV1HealthLiveGetData, LivenessApiV1HealthLiveGetResponses, LoginApiV1AuthLoginPostData, LoginApiV1AuthLoginPostErrors, LoginApiV1AuthLoginPostResponses, LogoutApiV1AuthLogoutPostData, LogoutApiV1AuthLogoutPostResponses, MarkAllReadApiV1NotificationsMarkAllReadPostData, MarkAllReadApiV1NotificationsMarkAllReadPostResponses, MarkNotificationReadApiV1NotificationsNotificationIdReadPutData, MarkNotificationReadApiV1NotificationsNotificationIdReadPutErrors, MarkNotificationReadApiV1NotificationsNotificationIdReadPutResponses, NotificationStreamApiV1EventsNotificationsStreamGetData, NotificationStreamApiV1EventsNotificationsStreamGetResponses, PauseReplaySessionApiV1ReplaySessionsSessionIdPausePostData, PauseReplaySessionApiV1ReplaySessionsSessionIdPausePostErrors, PauseReplaySessionApiV1ReplaySessionsSessionIdPausePostResponses, PublishCustomEventApiV1EventsPublishPostData, PublishCustomEventApiV1EventsPublishPostErrors, PublishCustomEventApiV1EventsPublishPostResponses, QueryEventsApiV1EventsQueryPostData, QueryEventsApiV1EventsQueryPostErrors, QueryEventsApiV1EventsQueryPostResponses, RegisterApiV1AuthRegisterPostData, RegisterApiV1AuthRegisterPostErrors, RegisterApiV1AuthRegisterPostResponses, ReplayAggregateEventsApiV1EventsReplayAggregateIdPostData, ReplayAggregateEventsApiV1EventsReplayAggregateIdPostErrors, ReplayAggregateEventsApiV1EventsReplayAggregateIdPostResponses, ReplayEventsApiV1AdminEventsReplayPostData, ReplayEventsApiV1AdminEventsReplayPostErrors, ReplayEventsApiV1AdminEventsReplayPostResponses, ResetSystemSettingsApiV1AdminSettingsResetPostData, ResetSystemSettingsApiV1AdminSettingsResetPostErrors, ResetSystemSettingsApiV1AdminSettingsResetPostResponses, ResetUserPasswordApiV1AdminUsersUserIdResetPasswordPostData, ResetUserPasswordApiV1AdminUsersUserIdResetPasswordPostErrors, ResetUserPasswordApiV1AdminUsersUserIdResetPasswordPostResponses, ResetUserRateLimitsApiV1AdminUsersUserIdRateLimitsResetPostData, ResetUserRateLimitsApiV1AdminUsersUserIdRateLimitsResetPostErrors, ResetUserRateLimitsApiV1AdminUsersUserIdRateLimitsResetPostResponses, RestoreSettingsApiV1UserSettingsRestorePostData, RestoreSettingsApiV1UserSettingsRestorePostErrors, RestoreSettingsApiV1UserSettingsRestorePostResponses, ResumeReplaySessionApiV1ReplaySessionsSessionIdResumePostData, ResumeReplaySessionApiV1ReplaySessionsSessionIdResumePostErrors, ResumeReplaySessionApiV1ReplaySessionsSessionIdResumePostResponses, RetryDlqMessagesApiV1DlqRetryPostData, RetryDlqMessagesApiV1DlqRetryPostErrors, RetryDlqMessagesApiV1DlqRetryPostResponses, RetryExecutionApiV1ExecutionsExecutionIdRetryPostData, RetryExecutionApiV1ExecutionsExecutionIdRetryPostErrors, RetryExecutionApiV1ExecutionsExecutionIdRetryPostResponses, SetRetryPolicyApiV1DlqRetryPolicyPostData, SetRetryPolicyApiV1DlqRetryPolicyPostErrors, SetRetryPolicyApiV1DlqRetryPolicyPostResponses, StartReplaySessionApiV1ReplaySessionsSessionIdStartPostData, StartReplaySessionApiV1ReplaySessionsSessionIdStartPostErrors, StartReplaySessionApiV1ReplaySessionsSessionIdStartPostResponses, UpdateCustomSettingApiV1UserSettingsCustomKeyPutData, UpdateCustomSettingApiV1UserSettingsCustomKeyPutErrors, UpdateCustomSettingApiV1UserSettingsCustomKeyPutResponses, UpdateEditorSettingsApiV1UserSettingsEditorPutData, UpdateEditorSettingsApiV1UserSettingsEditorPutErrors, UpdateEditorSettingsApiV1UserSettingsEditorPutResponses, UpdateNotificationSettingsApiV1UserSettingsNotificationsPutData, UpdateNotificationSettingsApiV1UserSettingsNotificationsPutErrors, UpdateNotificationSettingsApiV1UserSettingsNotificationsPutResponses, UpdateSavedScriptApiV1ScriptsScriptIdPutData, UpdateSavedScriptApiV1ScriptsScriptIdPutErrors, UpdateSavedScriptApiV1ScriptsScriptIdPutResponses, UpdateSubscriptionApiV1NotificationsSubscriptionsChannelPutData, UpdateSubscriptionApiV1NotificationsSubscriptionsChannelPutErrors, UpdateSubscriptionApiV1NotificationsSubscriptionsChannelPutResponses, UpdateSystemSettingsApiV1AdminSettingsPutData, UpdateSystemSettingsApiV1AdminSettingsPutErrors, UpdateSystemSettingsApiV1AdminSettingsPutResponses, UpdateThemeApiV1UserSettingsThemePutData, UpdateThemeApiV1UserSettingsThemePutErrors, UpdateThemeApiV1UserSettingsThemePutResponses, UpdateUserApiV1AdminUsersUserIdPutData, UpdateUserApiV1AdminUsersUserIdPutErrors, UpdateUserApiV1AdminUsersUserIdPutResponses, UpdateUserRateLimitsApiV1AdminUsersUserIdRateLimitsPutData, UpdateUserRateLimitsApiV1AdminUsersUserIdRateLimitsPutErrors, UpdateUserRateLimitsApiV1AdminUsersUserIdRateLimitsPutResponses, UpdateUserSettingsApiV1UserSettingsPutData, UpdateUserSettingsApiV1UserSettingsPutErrors, UpdateUserSettingsApiV1UserSettingsPutResponses } from './types.gen';
export type Options = Options2 & {
/**
@@ -54,13 +54,6 @@ export const registerApiV1AuthRegisterPost = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/auth/me', ...options });
-/**
- * Verify Token
- *
- * Verify the current access token.
- */
-export const verifyTokenApiV1AuthVerifyTokenGet = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/auth/verify-token', ...options });
-
/**
* Logout
*
@@ -271,20 +264,6 @@ export const cleanupOldSessionsApiV1ReplayCleanupPost = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/health/live', ...options });
-/**
- * Readiness
- *
- * Simple readiness probe. Extend with dependency checks if needed.
- */
-export const readinessApiV1HealthReadyGet = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/health/ready', ...options });
-
-/**
- * Get Dlq Statistics
- *
- * Get summary statistics for the dead letter queue.
- */
-export const getDlqStatisticsApiV1DlqStatsGet = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/dlq/stats', ...options });
-
/**
* Get Dlq Messages
*
@@ -432,27 +411,6 @@ export const publishCustomEventApiV1EventsPublishPost = (options: Options) => (options.client ?? client).post({
- url: '/api/v1/events/aggregate',
- ...options,
- headers: {
- 'Content-Type': 'application/json',
- ...options.headers
- }
-});
-
-/**
- * List Event Types
- *
- * List all distinct event types in the store.
- */
-export const listEventTypesApiV1EventsTypesListGet = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/events/types/list', ...options });
-
/**
* Replay Aggregate Events
*
@@ -814,18 +772,6 @@ export const deleteNotificationApiV1NotificationsNotificationIdDelete = (options: Options) => (options.client ?? client).get({ url: '/api/v1/sagas/{saga_id}', ...options });
@@ -833,21 +779,6 @@ export const getSagaStatusApiV1SagasSagaIdGet = (options: Options) => (options.client ?? client).get({ url: '/api/v1/sagas/execution/{execution_id}', ...options });
@@ -855,17 +786,6 @@ export const getExecutionSagasApiV1SagasExecutionExecutionIdGet = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/sagas/', ...options });
@@ -873,38 +793,5 @@ export const listSagasApiV1SagasGet = (opt
* Cancel Saga
*
* Cancel a running saga.
- *
- * Args:
- * saga_id: The saga identifier
- * request: FastAPI request object
- * saga_service: Saga service from DI
- * auth_service: Auth service from DI
- *
- * Returns:
- * Cancellation response with success status
- *
- * Raises:
- * HTTPException: 404 if not found, 403 if denied, 400 if invalid state
*/
export const cancelSagaApiV1SagasSagaIdCancelPost = (options: Options) => (options.client ?? client).post({ url: '/api/v1/sagas/{saga_id}/cancel', ...options });
-
-/**
- * Receive Grafana Alerts
- *
- * Receive and process a Grafana alerting webhook payload.
- */
-export const receiveGrafanaAlertsApiV1AlertsGrafanaPost = (options: Options) => (options.client ?? client).post({
- url: '/api/v1/alerts/grafana',
- ...options,
- headers: {
- 'Content-Type': 'application/json',
- ...options.headers
- }
-});
-
-/**
- * Test Grafana Alert Endpoint
- *
- * Verify the Grafana webhook endpoint is reachable.
- */
-export const testGrafanaAlertEndpointApiV1AlertsGrafanaTestGet = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/alerts/grafana/test', ...options });
diff --git a/frontend/src/lib/api/types.gen.ts b/frontend/src/lib/api/types.gen.ts
index 9eaee6af..9a7df13b 100644
--- a/frontend/src/lib/api/types.gen.ts
+++ b/frontend/src/lib/api/types.gen.ts
@@ -132,48 +132,6 @@ export type AdminUserOverview = {
} & DlqMessageDiscardedEvent)>;
};
-/**
- * AgeStatistics
- *
- * Age statistics for DLQ messages.
- */
-export type AgeStatistics = {
- /**
- * Min Age Seconds
- */
- min_age_seconds?: number;
- /**
- * Max Age Seconds
- */
- max_age_seconds?: number;
- /**
- * Avg Age Seconds
- */
- avg_age_seconds?: number;
-};
-
-/**
- * AlertResponse
- */
-export type AlertResponse = {
- /**
- * Message
- */
- message: string;
- /**
- * Alerts Received
- */
- alerts_received: number;
- /**
- * Alerts Processed
- */
- alerts_processed: number;
- /**
- * Errors
- */
- errors?: Array;
-};
-
/**
* AllocateResourcesCommandEvent
*/
@@ -293,7 +251,7 @@ export type CancelExecutionRequest = {
*
* Reason for cancellation
*/
- reason?: string | null;
+ reason?: string;
};
/**
@@ -1016,33 +974,6 @@ export type DlqRetryResult = {
error?: string | null;
};
-/**
- * DLQStats
- *
- * Statistics for the Dead Letter Queue.
- */
-export type DlqStats = {
- /**
- * By Status
- */
- by_status: {
- [key: string]: number;
- };
- /**
- * By Topic
- */
- by_topic: Array;
- /**
- * By Event Type
- */
- by_event_type: Array;
- age_stats: AgeStatistics;
- /**
- * Timestamp
- */
- timestamp: string;
-};
-
/**
* DLQTopicSummaryResponse
*
@@ -1317,26 +1248,6 @@ export type ErrorResponse = {
type?: string | null;
};
-/**
- * EventAggregationRequest
- *
- * Request model for event aggregation queries.
- */
-export type EventAggregationRequest = {
- /**
- * Pipeline
- *
- * MongoDB aggregation pipeline
- */
- pipeline: Array<{
- [key: string]: unknown;
- }>;
- /**
- * Limit
- */
- limit?: number;
-};
-
/**
* EventBrowseRequest
*
@@ -2178,22 +2089,6 @@ export type EventTypeCountSchema = {
count: number;
};
-/**
- * EventTypeStatistic
- *
- * Statistics for a single event type.
- */
-export type EventTypeStatistic = {
- /**
- * Event Type
- */
- event_type: string;
- /**
- * Count
- */
- count: number;
-};
-
/**
* ExampleScripts
*
@@ -2773,68 +2668,6 @@ export type ExecutionTimeoutEvent = {
stderr?: string;
};
-/**
- * GrafanaAlertItem
- */
-export type GrafanaAlertItem = {
- /**
- * Status
- */
- status?: string | null;
- /**
- * Labels
- */
- labels?: {
- [key: string]: string;
- };
- /**
- * Annotations
- */
- annotations?: {
- [key: string]: string;
- };
- /**
- * Valuestring
- */
- valueString?: string | null;
-};
-
-/**
- * GrafanaWebhook
- */
-export type GrafanaWebhook = {
- /**
- * Status
- */
- status?: string | null;
- /**
- * Receiver
- */
- receiver?: string | null;
- /**
- * Alerts
- */
- alerts?: Array;
- /**
- * Grouplabels
- */
- groupLabels?: {
- [key: string]: string;
- };
- /**
- * Commonlabels
- */
- commonLabels?: {
- [key: string]: string;
- };
- /**
- * Commonannotations
- */
- commonAnnotations?: {
- [key: string]: string;
- };
-};
-
/**
* HTTPValidationError
*/
@@ -2931,10 +2764,7 @@ export type LoginResponse = {
* Username
*/
username: string;
- /**
- * Role
- */
- role: string;
+ role: UserRole;
/**
* Csrf Token
*/
@@ -4112,26 +3942,6 @@ export type RateLimitUpdateResponse = {
config: UserRateLimitConfigResponse;
};
-/**
- * ReadinessResponse
- *
- * Response model for readiness probe.
- */
-export type ReadinessResponse = {
- /**
- * Status
- *
- * Readiness status
- */
- status: string;
- /**
- * Uptime Seconds
- *
- * Server uptime in seconds
- */
- uptime_seconds: number;
-};
-
/**
* ReleaseResourcesCommandEvent
*/
@@ -5657,6 +5467,16 @@ export type ServiceUnhealthyEvent = {
reason?: string;
};
+/**
+ * SessionConfigSummary
+ *
+ * Lightweight config included in session listings.
+ */
+export type SessionConfigSummary = {
+ replay_type: ReplayType;
+ target: ReplayTarget;
+};
+
/**
* SessionSummary
*
@@ -5667,8 +5487,7 @@ export type SessionSummary = {
* Session Id
*/
session_id: string;
- replay_type: ReplayType;
- target: ReplayTarget;
+ config: SessionConfigSummary;
status: ReplayStatus;
/**
* Total Events
@@ -5905,50 +5724,6 @@ export type ThemeUpdateRequest = {
theme: Theme;
};
-/**
- * TokenValidationResponse
- *
- * Response model for token validation
- */
-export type TokenValidationResponse = {
- /**
- * Valid
- */
- valid: boolean;
- /**
- * Username
- */
- username: string;
- /**
- * Role
- */
- role: string;
- /**
- * Csrf Token
- */
- csrf_token: string;
-};
-
-/**
- * TopicStatistic
- *
- * Statistics for a single topic.
- */
-export type TopicStatistic = {
- /**
- * Topic
- */
- topic: string;
- /**
- * Count
- */
- count: number;
- /**
- * Avg Retry Count
- */
- avg_retry_count: number;
-};
-
/**
* UnreadCountResponse
*
@@ -6288,11 +6063,11 @@ export type UserResponse = {
* Email
*/
email: string;
- role?: UserRole;
+ role: UserRole;
/**
* Is Active
*/
- is_active?: boolean;
+ is_active: boolean;
/**
* User Id
*/
@@ -6300,7 +6075,7 @@ export type UserResponse = {
/**
* Is Superuser
*/
- is_superuser?: boolean;
+ is_superuser: boolean;
/**
* Created At
*/
@@ -6596,8 +6371,7 @@ export type SessionSummaryWritable = {
* Session Id
*/
session_id: string;
- replay_type: ReplayType;
- target: ReplayTarget;
+ config: SessionConfigSummary;
status: ReplayStatus;
/**
* Total Events
@@ -6671,7 +6445,7 @@ export type RegisterApiV1AuthRegisterPostErrors = {
*/
400: ErrorResponse;
/**
- * Email already registered
+ * User already exists
*/
409: ErrorResponse;
/**
@@ -6707,31 +6481,6 @@ export type GetCurrentUserProfileApiV1AuthMeGetResponses = {
export type GetCurrentUserProfileApiV1AuthMeGetResponse = GetCurrentUserProfileApiV1AuthMeGetResponses[keyof GetCurrentUserProfileApiV1AuthMeGetResponses];
-export type VerifyTokenApiV1AuthVerifyTokenGetData = {
- body?: never;
- path?: never;
- query?: never;
- url: '/api/v1/auth/verify-token';
-};
-
-export type VerifyTokenApiV1AuthVerifyTokenGetErrors = {
- /**
- * Missing or invalid access token
- */
- 401: ErrorResponse;
-};
-
-export type VerifyTokenApiV1AuthVerifyTokenGetError = VerifyTokenApiV1AuthVerifyTokenGetErrors[keyof VerifyTokenApiV1AuthVerifyTokenGetErrors];
-
-export type VerifyTokenApiV1AuthVerifyTokenGetResponses = {
- /**
- * Successful Response
- */
- 200: TokenValidationResponse;
-};
-
-export type VerifyTokenApiV1AuthVerifyTokenGetResponse = VerifyTokenApiV1AuthVerifyTokenGetResponses[keyof VerifyTokenApiV1AuthVerifyTokenGetResponses];
-
export type LogoutApiV1AuthLogoutPostData = {
body?: never;
path?: never;
@@ -7580,38 +7329,6 @@ export type LivenessApiV1HealthLiveGetResponses = {
export type LivenessApiV1HealthLiveGetResponse = LivenessApiV1HealthLiveGetResponses[keyof LivenessApiV1HealthLiveGetResponses];
-export type ReadinessApiV1HealthReadyGetData = {
- body?: never;
- path?: never;
- query?: never;
- url: '/api/v1/health/ready';
-};
-
-export type ReadinessApiV1HealthReadyGetResponses = {
- /**
- * Successful Response
- */
- 200: ReadinessResponse;
-};
-
-export type ReadinessApiV1HealthReadyGetResponse = ReadinessApiV1HealthReadyGetResponses[keyof ReadinessApiV1HealthReadyGetResponses];
-
-export type GetDlqStatisticsApiV1DlqStatsGetData = {
- body?: never;
- path?: never;
- query?: never;
- url: '/api/v1/dlq/stats';
-};
-
-export type GetDlqStatisticsApiV1DlqStatsGetResponses = {
- /**
- * Successful Response
- */
- 200: DlqStats;
-};
-
-export type GetDlqStatisticsApiV1DlqStatsGetResponse = GetDlqStatisticsApiV1DlqStatsGetResponses[keyof GetDlqStatisticsApiV1DlqStatsGetResponses];
-
export type GetDlqMessagesApiV1DlqMessagesGetData = {
body?: never;
path?: never;
@@ -8319,53 +8036,6 @@ export type PublishCustomEventApiV1EventsPublishPostResponses = {
export type PublishCustomEventApiV1EventsPublishPostResponse = PublishCustomEventApiV1EventsPublishPostResponses[keyof PublishCustomEventApiV1EventsPublishPostResponses];
-export type AggregateEventsApiV1EventsAggregatePostData = {
- body: EventAggregationRequest;
- path?: never;
- query?: never;
- url: '/api/v1/events/aggregate';
-};
-
-export type AggregateEventsApiV1EventsAggregatePostErrors = {
- /**
- * Validation Error
- */
- 422: HttpValidationError;
-};
-
-export type AggregateEventsApiV1EventsAggregatePostError = AggregateEventsApiV1EventsAggregatePostErrors[keyof AggregateEventsApiV1EventsAggregatePostErrors];
-
-export type AggregateEventsApiV1EventsAggregatePostResponses = {
- /**
- * Response Aggregate Events Api V1 Events Aggregate Post
- *
- * Successful Response
- */
- 200: Array<{
- [key: string]: unknown;
- }>;
-};
-
-export type AggregateEventsApiV1EventsAggregatePostResponse = AggregateEventsApiV1EventsAggregatePostResponses[keyof AggregateEventsApiV1EventsAggregatePostResponses];
-
-export type ListEventTypesApiV1EventsTypesListGetData = {
- body?: never;
- path?: never;
- query?: never;
- url: '/api/v1/events/types/list';
-};
-
-export type ListEventTypesApiV1EventsTypesListGetResponses = {
- /**
- * Response List Event Types Api V1 Events Types List Get
- *
- * Successful Response
- */
- 200: Array;
-};
-
-export type ListEventTypesApiV1EventsTypesListGetResponse = ListEventTypesApiV1EventsTypesListGetResponses[keyof ListEventTypesApiV1EventsTypesListGetResponses];
-
export type ReplayAggregateEventsApiV1EventsReplayAggregateIdPostData = {
body?: never;
path: {
@@ -9713,48 +9383,3 @@ export type CancelSagaApiV1SagasSagaIdCancelPostResponses = {
};
export type CancelSagaApiV1SagasSagaIdCancelPostResponse = CancelSagaApiV1SagasSagaIdCancelPostResponses[keyof CancelSagaApiV1SagasSagaIdCancelPostResponses];
-
-export type ReceiveGrafanaAlertsApiV1AlertsGrafanaPostData = {
- body: GrafanaWebhook;
- path?: never;
- query?: never;
- url: '/api/v1/alerts/grafana';
-};
-
-export type ReceiveGrafanaAlertsApiV1AlertsGrafanaPostErrors = {
- /**
- * Validation Error
- */
- 422: HttpValidationError;
-};
-
-export type ReceiveGrafanaAlertsApiV1AlertsGrafanaPostError = ReceiveGrafanaAlertsApiV1AlertsGrafanaPostErrors[keyof ReceiveGrafanaAlertsApiV1AlertsGrafanaPostErrors];
-
-export type ReceiveGrafanaAlertsApiV1AlertsGrafanaPostResponses = {
- /**
- * Successful Response
- */
- 200: AlertResponse;
-};
-
-export type ReceiveGrafanaAlertsApiV1AlertsGrafanaPostResponse = ReceiveGrafanaAlertsApiV1AlertsGrafanaPostResponses[keyof ReceiveGrafanaAlertsApiV1AlertsGrafanaPostResponses];
-
-export type TestGrafanaAlertEndpointApiV1AlertsGrafanaTestGetData = {
- body?: never;
- path?: never;
- query?: never;
- url: '/api/v1/alerts/grafana/test';
-};
-
-export type TestGrafanaAlertEndpointApiV1AlertsGrafanaTestGetResponses = {
- /**
- * Response Test Grafana Alert Endpoint Api V1 Alerts Grafana Test Get
- *
- * Successful Response
- */
- 200: {
- [key: string]: string;
- };
-};
-
-export type TestGrafanaAlertEndpointApiV1AlertsGrafanaTestGetResponse = TestGrafanaAlertEndpointApiV1AlertsGrafanaTestGetResponses[keyof TestGrafanaAlertEndpointApiV1AlertsGrafanaTestGetResponses];
diff --git a/frontend/src/stores/__tests__/auth.test.ts b/frontend/src/stores/__tests__/auth.test.ts
index 6bfc7389..6f5aeb9e 100644
--- a/frontend/src/stores/__tests__/auth.test.ts
+++ b/frontend/src/stores/__tests__/auth.test.ts
@@ -4,13 +4,11 @@ import { suppressConsoleError, suppressConsoleWarn } from '$test/test-utils';
// Mock the API functions
const mockLoginApi = vi.fn();
const mockLogoutApi = vi.fn();
-const mockVerifyTokenApi = vi.fn();
const mockGetProfileApi = vi.fn();
vi.mock('../../lib/api', () => ({
loginApiV1AuthLoginPost: (...args: unknown[]) => mockLoginApi(...args),
logoutApiV1AuthLogoutPost: (...args: unknown[]) => mockLogoutApi(...args),
- verifyTokenApiV1AuthVerifyTokenGet: (...args: unknown[]) => mockVerifyTokenApi(...args),
getCurrentUserProfileApiV1AuthMeGet: (...args: unknown[]) => mockGetProfileApi(...args),
}));
@@ -28,6 +26,17 @@ vi.mock('../../lib/user-settings', () => ({
loadUserSettings: () => mockLoadUserSettings(),
}));
+const PROFILE_RESPONSE = {
+ user_id: 'user-123',
+ username: 'testuser',
+ email: 'test@example.com',
+ role: 'user',
+ is_active: true,
+ is_superuser: false,
+ created_at: '2024-01-01T00:00:00Z',
+ updated_at: '2024-01-01T00:00:00Z',
+};
+
describe('auth store', () => {
let sessionStorageData: Record = {};
@@ -45,7 +54,6 @@ describe('auth store', () => {
// Reset all mocks
mockLoginApi.mockReset();
mockLogoutApi.mockReset();
- mockVerifyTokenApi.mockReset();
mockGetProfileApi.mockReset();
mockClearUserSettings.mockReset();
mockLoadUserSettings.mockReset().mockResolvedValue({});
@@ -232,14 +240,7 @@ describe('auth store', () => {
describe('verifyAuth', () => {
it('returns true when verification succeeds', async () => {
- mockVerifyTokenApi.mockResolvedValue({
- data: { valid: true, username: 'testuser', role: 'user', csrf_token: 'token' },
- error: null,
- });
- mockGetProfileApi.mockResolvedValue({
- data: { user_id: 'user-123', email: 'test@example.com' },
- error: null,
- });
+ mockGetProfileApi.mockResolvedValue({ data: PROFILE_RESPONSE, error: null });
const { authStore } = await import('$stores/auth.svelte');
const result = await authStore.verifyAuth(true);
@@ -248,10 +249,7 @@ describe('auth store', () => {
});
it('returns false and clears state when verification fails', async () => {
- mockVerifyTokenApi.mockResolvedValue({
- data: { valid: false },
- error: null,
- });
+ mockGetProfileApi.mockResolvedValue({ data: null, error: { detail: 'Unauthorized' } });
const { authStore } = await import('$stores/auth.svelte');
const result = await authStore.verifyAuth(true);
@@ -261,56 +259,35 @@ describe('auth store', () => {
});
it('uses cache when not force refreshing', async () => {
- mockVerifyTokenApi.mockResolvedValue({
- data: { valid: true, username: 'testuser', role: 'user', csrf_token: 'token' },
- error: null,
- });
- mockGetProfileApi.mockResolvedValue({
- data: { user_id: 'user-123', email: 'test@example.com' },
- error: null,
- });
+ mockGetProfileApi.mockResolvedValue({ data: PROFILE_RESPONSE, error: null });
const { authStore } = await import('$stores/auth.svelte');
// First call - should hit API
await authStore.verifyAuth(true);
- expect(mockVerifyTokenApi).toHaveBeenCalledTimes(1);
+ expect(mockGetProfileApi).toHaveBeenCalledTimes(1);
// Second call without force - should use cache
await authStore.verifyAuth(false);
- expect(mockVerifyTokenApi).toHaveBeenCalledTimes(1);
+ expect(mockGetProfileApi).toHaveBeenCalledTimes(1);
});
it('bypasses cache when force refreshing', async () => {
- mockVerifyTokenApi.mockResolvedValue({
- data: { valid: true, username: 'testuser', role: 'user', csrf_token: 'token' },
- error: null,
- });
- mockGetProfileApi.mockResolvedValue({
- data: { user_id: 'user-123', email: 'test@example.com' },
- error: null,
- });
+ mockGetProfileApi.mockResolvedValue({ data: PROFILE_RESPONSE, error: null });
const { authStore } = await import('$stores/auth.svelte');
await authStore.verifyAuth(true);
await authStore.verifyAuth(true);
- expect(mockVerifyTokenApi).toHaveBeenCalledTimes(2);
+ expect(mockGetProfileApi).toHaveBeenCalledTimes(2);
});
it('returns cached value on network error (offline-first)', async () => {
const restoreConsole = suppressConsoleWarn();
// First successful verification
- mockVerifyTokenApi.mockResolvedValueOnce({
- data: { valid: true, username: 'testuser', role: 'user', csrf_token: 'token' },
- error: null,
- });
- mockGetProfileApi.mockResolvedValue({
- data: { user_id: 'user-123', email: 'test@example.com' },
- error: null,
- });
+ mockGetProfileApi.mockResolvedValueOnce({ data: PROFILE_RESPONSE, error: null });
const { authStore } = await import('$stores/auth.svelte');
@@ -318,7 +295,7 @@ describe('auth store', () => {
expect(firstResult).toBe(true);
// Second call with network error
- mockVerifyTokenApi.mockRejectedValueOnce(new Error('Network error'));
+ mockGetProfileApi.mockRejectedValueOnce(new Error('Network error'));
const secondResult = await authStore.verifyAuth(true);
expect(secondResult).toBe(true); // Should return cached value
@@ -359,7 +336,7 @@ describe('auth store', () => {
describe('initialize', () => {
it('returns false when no persisted auth and verification fails', async () => {
- mockVerifyTokenApi.mockResolvedValue({ data: { valid: false }, error: null });
+ mockGetProfileApi.mockResolvedValue({ data: null, error: { detail: 'Unauthorized' } });
const { authStore } = await import('$stores/auth.svelte');
const result = await authStore.initialize();
@@ -368,14 +345,7 @@ describe('auth store', () => {
});
it('returns true when no persisted auth but verification succeeds', async () => {
- mockVerifyTokenApi.mockResolvedValue({
- data: { valid: true, username: 'testuser', role: 'user', csrf_token: 'token' },
- error: null,
- });
- mockGetProfileApi.mockResolvedValue({
- data: { user_id: 'user-123', email: 'test@example.com' },
- error: null,
- });
+ mockGetProfileApi.mockResolvedValue({ data: PROFILE_RESPONSE, error: null });
const { authStore } = await import('$stores/auth.svelte');
const result = await authStore.initialize();
@@ -385,34 +355,23 @@ describe('auth store', () => {
});
it('only initializes once', async () => {
- mockVerifyTokenApi.mockResolvedValue({
- data: { valid: true, username: 'testuser', role: 'user', csrf_token: 'token' },
- error: null,
- });
- mockGetProfileApi.mockResolvedValue({
- data: { user_id: 'user-123', email: 'test@example.com' },
- error: null,
- });
+ mockGetProfileApi.mockResolvedValue({ data: PROFILE_RESPONSE, error: null });
const { authStore } = await import('$stores/auth.svelte');
await authStore.initialize();
await authStore.initialize();
await authStore.initialize();
- expect(mockVerifyTokenApi).toHaveBeenCalledTimes(1);
+ expect(mockGetProfileApi).toHaveBeenCalledTimes(1);
});
it('handles concurrent initialization calls', async () => {
- mockVerifyTokenApi.mockImplementation(() =>
+ mockGetProfileApi.mockImplementation(() =>
new Promise(resolve => setTimeout(() => resolve({
- data: { valid: true, username: 'testuser', role: 'user', csrf_token: 'token' },
+ data: PROFILE_RESPONSE,
error: null,
}), 50))
);
- mockGetProfileApi.mockResolvedValue({
- data: { user_id: 'user-123', email: 'test@example.com' },
- error: null,
- });
const { authStore } = await import('$stores/auth.svelte');
const results = await Promise.all([
@@ -422,7 +381,7 @@ describe('auth store', () => {
]);
expect(results).toEqual([true, true, true]);
- expect(mockVerifyTokenApi).toHaveBeenCalledTimes(1);
+ expect(mockGetProfileApi).toHaveBeenCalledTimes(1);
});
it('restores from persisted auth and verifies', async () => {
@@ -437,12 +396,8 @@ describe('auth store', () => {
};
sessionStorageData['authState'] = JSON.stringify(authState);
- mockVerifyTokenApi.mockResolvedValue({
- data: { valid: true, username: 'testuser', role: 'admin', csrf_token: 'csrf-token' },
- error: null,
- });
mockGetProfileApi.mockResolvedValue({
- data: { user_id: 'user-123', email: 'test@example.com' },
+ data: { ...PROFILE_RESPONSE, role: 'admin' },
error: null,
});
@@ -466,7 +421,7 @@ describe('auth store', () => {
};
sessionStorageData['authState'] = JSON.stringify(authState);
- mockVerifyTokenApi.mockResolvedValue({ data: { valid: false }, error: null });
+ mockGetProfileApi.mockResolvedValue({ data: null, error: { detail: 'Unauthorized' } });
const { authStore } = await import('$stores/auth.svelte');
const result = await authStore.initialize();
@@ -490,7 +445,7 @@ describe('auth store', () => {
};
sessionStorageData['authState'] = JSON.stringify(authState);
- mockVerifyTokenApi.mockRejectedValue(new Error('Network error'));
+ mockGetProfileApi.mockRejectedValue(new Error('Network error'));
const { authStore } = await import('$stores/auth.svelte');
const result = await authStore.initialize();
@@ -513,7 +468,7 @@ describe('auth store', () => {
};
sessionStorageData['authState'] = JSON.stringify(authState);
- mockVerifyTokenApi.mockRejectedValue(new Error('Network error'));
+ mockGetProfileApi.mockRejectedValue(new Error('Network error'));
const { authStore } = await import('$stores/auth.svelte');
const result = await authStore.initialize();
@@ -527,38 +482,24 @@ describe('auth store', () => {
describe('waitForInit', () => {
it('returns immediately if already initialized', async () => {
- mockVerifyTokenApi.mockResolvedValue({
- data: { valid: true, username: 'testuser', role: 'user', csrf_token: 'token' },
- error: null,
- });
- mockGetProfileApi.mockResolvedValue({
- data: { user_id: 'user-123', email: 'test@example.com' },
- error: null,
- });
+ mockGetProfileApi.mockResolvedValue({ data: PROFILE_RESPONSE, error: null });
const { authStore } = await import('$stores/auth.svelte');
await authStore.initialize();
const result = await authStore.waitForInit();
expect(result).toBe(true);
- expect(mockVerifyTokenApi).toHaveBeenCalledTimes(1);
+ expect(mockGetProfileApi).toHaveBeenCalledTimes(1);
});
it('initializes if not started', async () => {
- mockVerifyTokenApi.mockResolvedValue({
- data: { valid: true, username: 'testuser', role: 'user', csrf_token: 'token' },
- error: null,
- });
- mockGetProfileApi.mockResolvedValue({
- data: { user_id: 'user-123', email: 'test@example.com' },
- error: null,
- });
+ mockGetProfileApi.mockResolvedValue({ data: PROFILE_RESPONSE, error: null });
const { authStore } = await import('$stores/auth.svelte');
const result = await authStore.waitForInit();
expect(result).toBe(true);
- expect(mockVerifyTokenApi).toHaveBeenCalled();
+ expect(mockGetProfileApi).toHaveBeenCalled();
});
});
diff --git a/frontend/src/stores/auth.svelte.ts b/frontend/src/stores/auth.svelte.ts
index 8fe76591..9c24e759 100644
--- a/frontend/src/stores/auth.svelte.ts
+++ b/frontend/src/stores/auth.svelte.ts
@@ -1,7 +1,6 @@
import {
loginApiV1AuthLoginPost,
logoutApiV1AuthLogoutPost,
- verifyTokenApiV1AuthVerifyTokenGet,
getCurrentUserProfileApiV1AuthMeGet,
} from '$lib/api';
import { clearUserSettings } from '$stores/userSettings.svelte';
@@ -206,8 +205,8 @@ class AuthStore {
this.#verifyPromise = (async () => {
try {
- const { data, error } = await verifyTokenApiV1AuthVerifyTokenGet({});
- if (error || !data.valid) {
+ const { data, error } = await getCurrentUserProfileApiV1AuthMeGet({});
+ if (error || !data) {
this.clearAuth();
this.#authCache = { valid: false, timestamp: Date.now() };
return false;
@@ -215,22 +214,18 @@ class AuthStore {
this.isAuthenticated = true;
this.username = data.username;
this.userRole = data.role;
- this.csrfToken = data.csrf_token;
- const current = getPersistedAuthState();
+ this.userId = data.user_id;
+ this.userEmail = data.email;
+ this.csrfToken = document.cookie.match(/(?:^|;\s*)csrf_token=([^;]*)/)?.[1] ?? null;
persistAuthState({
isAuthenticated: true,
username: data.username,
userRole: data.role,
- csrfToken: data.csrf_token,
- userId: current?.userId ?? null,
- userEmail: current?.userEmail ?? null
+ csrfToken: this.csrfToken,
+ userId: data.user_id,
+ userEmail: data.email,
});
this.#authCache = { valid: true, timestamp: Date.now() };
- try {
- await this.fetchUserProfile();
- } catch (err) {
- console.warn('Failed to fetch user profile during verification:', err);
- }
return true;
} catch (err) {
// Network error - use cached state if available (offline-first)