Acá es donde uMind deja de ser una herramienta interna: el cliente entra a /portal/studio con su sesión de portal y gestiona lo suyo. - UmindScopePortal/UmindScopeStaff es el ÚNICO punto donde se decide el alcance. El del cliente sale de GetClienteIDsForPortalUser, el mismo que ya autoriza el resto del portal. nil = staff sin restricción, slice vacío = no ve nada; una ruta sin scope también cae en "no ve nada" para que olvidarse el middleware falle visible y no abra todo. - Un solo set de handlers montado bajo /app/umind y /portal/umind (RegistrarRutasUmind). Duplicarlos sería duplicar las chances de olvidar un chequeo. - Guarda de acceso en TODOS los handlers, incluidos los sub-recursos que llegan por :id (documento, tool, canal, conexión): hay que cargarlos para saber de quién son, si no un cliente podría borrar el canal de otro adivinando el id. Responden 404, no 403: un 403 confirmaría que el recurso existe. - Cierra un bug preexistente: las lecturas GET /app/umind/* no tenían SoloAdmin ni pasaban por MenuMiddleware, así que cualquier usuario de staff podía leer los tenants de todos los clientes. - Límite de agentes por plan (409 con mensaje claro). Un tenant sin plan no tiene límite: cortarles de golpe sería peor que dejarlos como estaban. - /umind/ai-configs reemplaza con alcance a /app/api/ai-config/select, que devolvía TODAS las configs del sistema. - El SPA deduce por la URL si es staff o cliente (base del router, prefijo de API y URL de login) y oculta lo que es solo de staff. - Test de aislamiento entre clientes: 7 casos, incluido que un scope vacío no se confunda con staff. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
74 lines
3.8 KiB
Go
74 lines
3.8 KiB
Go
package routes
|
|
|
|
import (
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/sujit-baniya/fiber-boilerplate/rest/controllers"
|
|
"github.com/sujit-baniya/fiber-boilerplate/rest/middlewares"
|
|
)
|
|
|
|
func PortalRoutes(app fiber.Router) {
|
|
// ─── Rutas públicas ────────────────────────────────────────────────────────
|
|
app.Get("/portal/login", controllers.PortalLoginPage)
|
|
app.Post("/portal/login", controllers.PortalLoginPost)
|
|
app.Get("/portal/logout", controllers.PortalLogout)
|
|
app.Get("/portal/forgot-password", controllers.PortalForgotPasswordPage)
|
|
app.Post("/portal/forgot-password", controllers.PortalForgotPasswordPost)
|
|
app.Get("/portal/reset-password", controllers.PortalResetPasswordPage)
|
|
app.Post("/portal/reset-password", controllers.PortalResetPasswordPost)
|
|
|
|
// ─── Rutas protegidas ──────────────────────────────────────────────────────
|
|
portal := app.Group("/portal").Use(middlewares.PortalAuth())
|
|
|
|
portal.Get("/dashboard", controllers.PortalDashboard)
|
|
portal.Get("/proyecto/:slug", controllers.PortalProyecto)
|
|
|
|
// API JSON para Alpine.js
|
|
portal.Get("/api/proyectos", controllers.PortalGetProyectos)
|
|
portal.Get("/api/proyecto/:slug", controllers.PortalGetProyectoData)
|
|
|
|
// Tickets
|
|
portal.Post("/tickets", controllers.PortalCrearTicket)
|
|
portal.Post("/tickets/:id/mensaje", controllers.PortalResponderTicket)
|
|
portal.Post("/tickets/:id/leido", controllers.PortalMarcarTicketLeido)
|
|
|
|
// Descargas
|
|
portal.Get("/entregables/:id/download", controllers.PortalDownloadEntregable)
|
|
portal.Get("/facturas/:id/download", controllers.PortalDownloadFactura)
|
|
portal.Get("/documentos/:id/download", controllers.PortalDownloadDocumento)
|
|
|
|
// Notificaciones in-app del portal user
|
|
portal.Get("/mis-notifs", controllers.PortalGetMisNotifs)
|
|
portal.Put("/mis-notifs/:id/leida", controllers.PortalMarcarNotifLeida)
|
|
portal.Post("/mis-notifs/marcar-todas", controllers.PortalMarcarTodasLeidas)
|
|
portal.Post("/mis-notifs/marcar-facturas", controllers.PortalMarcarFacturasLeidas)
|
|
|
|
// Partner: recursos y comunicados (solo usuarios con rol partner)
|
|
portal.Get("/partner-recursos", middlewares.PortalPartnerOnly(), controllers.PortalPartnerRecursos)
|
|
portal.Get("/partner-recursos/:id/download", middlewares.PortalPartnerOnly(), controllers.PortalDownloadPartnerRecurso)
|
|
portal.Get("/partner-comunicados/:id/download", middlewares.PortalPartnerOnly(), controllers.DownloadPartnerComunicadoArchivo)
|
|
|
|
// Mi perfil
|
|
portal.Get("/mi-perfil", controllers.PortalGetMiPerfil)
|
|
portal.Put("/mi-perfil", controllers.PortalUpdateMiPerfil)
|
|
portal.Put("/mi-perfil/password", controllers.PortalCambiarPassword)
|
|
portal.Post("/mi-perfil/rut-file", controllers.PortalSubirRutDocumento)
|
|
|
|
// Telegram: vinculación guiada
|
|
portal.Post("/mi-perfil/telegram-init", controllers.PortalTelegramInit)
|
|
portal.Get("/mi-perfil/telegram-status", controllers.PortalTelegramStatus)
|
|
portal.Post("/mi-perfil/telegram-validar", controllers.PortalTelegramValidar)
|
|
|
|
// ─── uMind Studio para el cliente ──────────────────────────────────────────
|
|
// Los MISMOS handlers que usa el staff, pero con UmindScopePortal: el
|
|
// cliente solo alcanza los tenants de sus Cliente. Sin middleware de
|
|
// escritura extra — administrar lo suyo es justamente el punto.
|
|
RegistrarRutasUmind(portal, middlewares.UmindScopePortal, nil)
|
|
|
|
// El SPA (mismo build que /orchestrator) con fallback para vue-router.
|
|
studioFallback := func(c *fiber.Ctx) error {
|
|
return c.SendFile("./public/orchestrator/index.html")
|
|
}
|
|
portal.Get("/studio", studioFallback)
|
|
portal.Get("/studio/*", studioFallback)
|
|
}
|