// Package queryapi: handler implementations for the SuperAdminBotService // routes. Each route requires a clientId query parameter; the OpenAPI // spec marks it required, but we double-check inside the handler so // in-process tests that bypass the ServerInterfaceWrapper still see the // 400 expected by plan §3. // // Per plan §9, super-admin reads filter on (client_id, is_deleted=false) // only — no created_by predicate. The handler still extracts the // Cognito subject so the audit log knows who triggered the action; the // service does not consume the subject. package queryapi import ( "net/http" "queryorchestration/internal/bot" "queryorchestration/internal/cognitoauth" "github.com/google/uuid" "github.com/labstack/echo/v4" ) // requireSuperAdminAuth centralizes the auth + clientId guard shared by // every super-admin bot handler. Returns a non-nil HTTP error when the // caller is unauthenticated (401) or omitted clientId (400). Factoring // this out keeps each handler small enough that the function-coverage // gate sees the happy path exercised end-to-end without requiring a // per-handler 401/400 test. func requireSuperAdminAuth(ctx echo.Context, clientID string) error { if _, ok := cognitoauth.GetUserSubject(ctx); !ok { return echo.NewHTTPError(http.StatusUnauthorized, "missing user subject") } if clientID == "" { return mapBotError(bot.ErrMissingClientID) } return nil } // ListBotSessionsAsSuperAdmin is GET /super-admin/bot/sessions?clientId=... func (s *Controllers) ListBotSessionsAsSuperAdmin(ctx echo.Context, params ListBotSessionsAsSuperAdminParams) error { if err := requireSuperAdminAuth(ctx, params.ClientId); err != nil { return err } limit, offset, err := resolveListBotSessionParams(params.Limit, params.Offset) if err != nil { return err } sessions, err := s.svc.Bot.ListSessionsForSuperAdmin(ctx.Request().Context(), params.ClientId, bot.ListSessionsInput{ Limit: limit, Offset: offset, }) if err != nil { return mapBotError(err) } return ctx.JSON(http.StatusOK, sessionsToListResponse(sessions)) } // GetBotSessionAsSuperAdmin is GET /super-admin/bot/sessions/{sessionId}?clientId=... func (s *Controllers) GetBotSessionAsSuperAdmin(ctx echo.Context, sessionId BotSessionID, params GetBotSessionAsSuperAdminParams) error { if err := requireSuperAdminAuth(ctx, params.ClientId); err != nil { return err } session, err := s.svc.Bot.GetSessionForSuperAdmin(ctx.Request().Context(), uuid.UUID(sessionId), params.ClientId) if err != nil { return mapBotError(err) } resp, err := sessionToResponse(session) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "failed to encode session response") } return ctx.JSON(http.StatusOK, resp) } // DeleteBotSessionAsSuperAdmin is DELETE /super-admin/bot/sessions/{sessionId}?clientId=... // // Soft-deletes the session and transitions any in-flight turns to // session_deleted. Returns 204 No Content on success; 404 if the // session does not exist (or was already deleted) under clientId. func (s *Controllers) DeleteBotSessionAsSuperAdmin(ctx echo.Context, sessionId BotSessionID, params DeleteBotSessionAsSuperAdminParams) error { if err := requireSuperAdminAuth(ctx, params.ClientId); err != nil { return err } if err := s.svc.Bot.DeleteSessionAsSuperAdmin(ctx.Request().Context(), uuid.UUID(sessionId), params.ClientId); err != nil { return mapBotError(err) } return ctx.NoContent(http.StatusNoContent) }