package controllers import ( "fmt" "github.com/gofiber/fiber/v2" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" "github.com/sujit-baniya/fiber-boilerplate/rest/middlewares" ) // errSinAcceso es la respuesta uniforme cuando el que llama no tiene alcance // sobre el recurso. Se usa 404 y no 403 a propósito: un 403 confirmaría que // el tenant/agente existe, y eso ya es información de otro cliente. func errSinAcceso(c *fiber.Ctx) error { return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "no encontrado"}) } // accesoTenant valida que el que llama pueda operar sobre ese tenant. func accesoTenant(c *fiber.Ctx, tenantID uint) error { if tenantID == 0 || !middlewares.UmindPuedeVerTenant(c, tenantID) { return errSinAcceso(c) } return nil } // accesoAgente resuelve el agente a su tenant y valida el alcance. Devuelve el // agente ya cargado para no volver a buscarlo en el handler. func accesoAgente(c *fiber.Ctx, agenteID uint) (*models.UmindAgente, error) { if agenteID == 0 { return nil, errSinAcceso(c) } agente, err := models.GetUmindAgenteByID(agenteID) if err != nil { return nil, errSinAcceso(c) } if !middlewares.UmindPuedeVerTenant(c, agente.TenantID) { return nil, errSinAcceso(c) } return agente, nil } // Los sub-recursos (documento, tool, canal, conexión) llegan por :id y no // dicen a qué agente pertenecen: hay que cargarlos para resolver el dueño // antes de dejar operar sobre ellos. Sin esto, un cliente podría borrar el // canal de otro adivinando el id. func accesoDocumento(c *fiber.Ctx, id uint) error { doc, err := models.GetUmindDocumentoByID(id) if err != nil { return errSinAcceso(c) } _, e := accesoAgente(c, doc.AgenteID) return e } func accesoHerramienta(c *fiber.Ctx, id uint) error { h, err := models.GetUmindHerramientaByID(id) if err != nil { return errSinAcceso(c) } _, e := accesoAgente(c, h.AgenteID) return e } func accesoCanal(c *fiber.Ctx, id uint) error { canal, err := models.GetUmindCanalByID(id) if err != nil { return errSinAcceso(c) } _, e := accesoAgente(c, canal.AgenteID) return e } // verificarCupoAgentes aplica el límite de agentes del plan. Un tenant sin // plan asignado (los creados antes de que existieran los planes) no tiene // límite: cortarles el paso de golpe sería peor que dejarlos como estaban. func verificarCupoAgentes(c *fiber.Ctx, tenantID uint) error { plan := models.GetPlanDeTenant(tenantID) if plan == nil || plan.MaxAgentes <= 0 { return nil } agentes, err := models.GetUmindAgentesByTenant(tenantID) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } if len(agentes) >= plan.MaxAgentes { return c.Status(fiber.StatusConflict).JSON(fiber.Map{ "error": fmt.Sprintf("Tu plan %s permite %d agente(s) y ya los estás usando. Escribinos para ampliarlo.", plan.Nombre, plan.MaxAgentes), }) } return nil } // tenantsVisibles filtra una lista de tenants al alcance del que llama. func tenantsVisibles(c *fiber.Ctx, items []models.UmindTenant) []models.UmindTenant { if _, esStaff := middlewares.UmindScopeDe(c); esStaff { return items } out := make([]models.UmindTenant, 0, len(items)) for _, t := range items { if middlewares.UmindPuedeVerTenant(c, t.ID) { out = append(out, t) } } return out } func accesoConexion(c *fiber.Ctx, id uint) error { cx, err := models.GetUmindConexionByID(id) if err != nil { return errSinAcceso(c) } _, e := accesoAgente(c, cx.AgenteID) return e } // GetUmindAiConfigsHandler devuelve las configs de IA que el que llama puede // usar: las globales del staff más las propias de sus tenants. Es el // reemplazo con alcance de /app/api/ai-config/select, que devolvía TODAS las // configs del sistema — servírselo a un cliente le mostraría las de los demás. func GetUmindAiConfigsHandler(c *fiber.Ctx) error { tenantIDs, esStaff := middlewares.UmindScopeDe(c) var items []models.AiConfig var err error if esStaff { items, err = models.GetAiConfigSelect() } else { items, err = models.GetAiConfigSelectPorTenants(tenantIDs) } if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } out := make([]fiber.Map, len(items)) for i, it := range items { out[i] = fiber.Map{"ID": it.ID, "nombre": it.Nombre, "provider": it.Provider} } return c.JSON(fiber.Map{"items": out}) }