package handler import ( "github.com/gofiber/fiber/v2" "github.com/google/uuid" "github.com/gosec/gsc-ops-api/internal/middleware" "github.com/gosec/gsc-ops-api/internal/service" "github.com/gosec/gsc-ops-api/pkg/types" ) // PersonaHandler handles persona management endpoints type PersonaHandler struct { svc *service.PersonaService } // NewPersonaHandler creates a new persona handler func NewPersonaHandler(svc *service.PersonaService) *PersonaHandler { return &PersonaHandler{svc: svc} } // parseTenantID extracts and validates tenantId from query or body func parseTenantID(c *fiber.Ctx) (uuid.UUID, *types.APIError) { tid := c.Query("tenantId") if tid == "" { return uuid.Nil, types.NewBadRequest("tenantId query parameter is required") } parsed, err := uuid.Parse(tid) if err != nil { return uuid.Nil, types.NewBadRequest("Invalid tenantId") } return parsed, nil } // ListPersonas handles GET /api/v1/personas func (h *PersonaHandler) ListPersonas(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) tenantID, apiErr := parseTenantID(c) if apiErr != nil { return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } params := types.ListParams{ Limit: c.QueryInt("limit", 50), Offset: c.QueryInt("offset", 0), Status: c.Query("status"), } personas, total, err := h.svc.ListPersonas(c.Context(), tenantID, params) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewPagedResponse(personas, total, params.Limit, params.Offset, reqID)) } // GetPersona handles GET /api/v1/personas/:id func (h *PersonaHandler) GetPersona(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := uuid.Parse(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid persona ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } tenantID, apiErr := parseTenantID(c) if apiErr != nil { return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } persona, err := h.svc.GetPersona(c.Context(), id, tenantID) if err != nil { apiErr := types.NewNotFound("Persona not found") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(persona, reqID)) } // CreatePersona handles POST /api/v1/personas func (h *PersonaHandler) CreatePersona(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) var req types.PersonaCreate if err := c.BodyParser(&req); err != nil { apiErr := types.NewBadRequest("Invalid request body: " + err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if req.TenantID == uuid.Nil { apiErr := types.NewValidation("tenantId is required") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if req.Name == "" { apiErr := types.NewValidation("name is required") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } persona, err := h.svc.CreatePersona(c.Context(), &req) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.Status(fiber.StatusCreated).JSON(types.NewDataResponse(persona, reqID)) } // UpdatePersona handles PUT /api/v1/personas/:id func (h *PersonaHandler) UpdatePersona(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := uuid.Parse(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid persona ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } tenantID, apiErr := parseTenantID(c) if apiErr != nil { return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } var req types.PersonaUpdate if err := c.BodyParser(&req); err != nil { apiErr := types.NewBadRequest("Invalid request body: " + err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } persona, err := h.svc.UpdatePersona(c.Context(), id, tenantID, &req) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(persona, reqID)) } // DeletePersona handles DELETE /api/v1/personas/:id func (h *PersonaHandler) DeletePersona(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := uuid.Parse(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid persona ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } tenantID, apiErr := parseTenantID(c) if apiErr != nil { return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if err := h.svc.DeletePersona(c.Context(), id, tenantID); err != nil { apiErr := types.NewNotFound("Persona not found") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.SendStatus(fiber.StatusNoContent) } // GetSelfModel handles GET /api/v1/personas/:id/self-model func (h *PersonaHandler) GetSelfModel(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := uuid.Parse(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid persona ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } tenantID, apiErr := parseTenantID(c) if apiErr != nil { return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } snapshot, err := h.svc.GetSelfModel(c.Context(), id, tenantID) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(snapshot, reqID)) } // GetExperiences handles GET /api/v1/personas/:id/experiences func (h *PersonaHandler) GetExperiences(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := uuid.Parse(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid persona ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } tenantID, apiErr := parseTenantID(c) if apiErr != nil { return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } limit := c.QueryInt("limit", 20) experiences, err := h.svc.SearchExperiences(c.Context(), id, tenantID, limit) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(experiences, reqID)) } // GetEvaluations handles GET /api/v1/personas/:id/evaluations/:sessionId func (h *PersonaHandler) GetEvaluations(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) sessionID, err := uuid.Parse(c.Params("sessionId")) if err != nil { apiErr := types.NewBadRequest("Invalid session ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } limit := c.QueryInt("limit", 10) evaluations, err := h.svc.GetEvaluations(c.Context(), sessionID, limit) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(evaluations, reqID)) } // GetMoralPattern handles GET /api/v1/personas/:id/moral-pattern/:sessionId func (h *PersonaHandler) GetMoralPattern(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) sessionID, err := uuid.Parse(c.Params("sessionId")) if err != nil { apiErr := types.NewBadRequest("Invalid session ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } tenantID, apiErr := parseTenantID(c) if apiErr != nil { return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } assessments, err := h.svc.GetMoralPattern(c.Context(), sessionID, tenantID) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(map[string]interface{}{ "assessments": assessments, }, reqID)) }