Files
soft_usite/rest/controllers/doc_pagina_controller.go
T
2026-05-06 14:58:55 -05:00

360 lines
11 KiB
Go

package controllers
import (
"math"
"strconv"
"github.com/gofiber/fiber/v2"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
)
// DocPaginasIndex renderiza el panel de gestión de páginas de documentación.
func DocPaginasIndex(c *fiber.Ctx) error {
data := fiber.Map{
"user": c.Locals("user").(map[string]interface{}),
"modules": c.Locals("userModules"),
}
if err := c.Render("docs/paginas", data, "layouts/main"); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return nil
}
// GetDocPaginas devuelve la lista paginada de páginas en JSON.
func GetDocPaginas(c *fiber.Ctx) error {
page, _ := strconv.Atoi(c.Query("page", "1"))
if page < 1 {
page = 1
}
search := c.Query("search", "")
limit := 10
offset := (page - 1) * limit
var saasID, catID uint
if v, err := strconv.ParseUint(c.Query("saas_id", "0"), 10, 32); err == nil {
saasID = uint(v)
}
if v, err := strconv.ParseUint(c.Query("categoria_id", "0"), 10, 32); err == nil {
catID = uint(v)
}
items, total, err := models.GetAllDocPaginas(limit, offset, search, saasID, catID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
// Selects para el formulario
saasItems, _ := models.GetAllSaasProductosSelect()
categorias, _ := models.GetAllDocCategoriasSelect()
roles, _ := models.AllRolesSelect()
return c.JSON(fiber.Map{
"items": items,
"saas": saasItems,
"categorias": categorias,
"roles": roles,
"total": total,
"totalPages": int(math.Ceil(float64(total) / float64(limit))),
"page": page,
"limit": limit,
})
}
// GetDocPaginaDetalle devuelve una página por ID con todos sus datos para edición.
func GetDocPaginaDetalle(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "ID inválido"})
}
item, err := models.GetDocPaginaByID(uint(id))
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Página no encontrada"})
}
return c.JSON(item)
}
// CreateDocPagina crea una nueva página de documentación.
func CreateDocPagina(c *fiber.Ctx) error {
type Req struct {
SaasID uint `json:"saas_id"`
CategoriaID uint `json:"categoria_id"`
Titulo string `json:"titulo"`
Slug string `json:"slug"`
Contenido string `json:"contenido"`
TipoContenido string `json:"tipo_contenido"`
Visibilidad string `json:"visibilidad"`
Orden int `json:"orden"`
Publicado bool `json:"publicado"`
RolIDs []uint `json:"rol_ids"`
}
var req Req
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Datos inválidos"})
}
if req.Titulo == "" || req.Slug == "" || req.SaasID == 0 || req.CategoriaID == 0 {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Título, slug, saas y categoría son obligatorios"})
}
if req.TipoContenido == "" {
req.TipoContenido = "markdown"
}
if req.Visibilidad == "" {
req.Visibilidad = "public"
}
userMap, _ := c.Locals("user").(map[string]interface{})
var creadoPor uint
if userMap != nil {
if idVal, ok := userMap["id"]; ok {
switch v := idVal.(type) {
case float64:
creadoPor = uint(v)
case uint:
creadoPor = v
}
}
}
item := &models.DocPagina{
SaasID: req.SaasID,
CategoriaID: req.CategoriaID,
Titulo: req.Titulo,
Slug: req.Slug,
Contenido: req.Contenido,
TipoContenido: req.TipoContenido,
Visibilidad: req.Visibilidad,
Orden: req.Orden,
Publicado: req.Publicado,
CreadoPor: creadoPor,
}
if err := models.CreateDocPagina(item, req.RolIDs); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"message": "Página creada", "item": item})
}
// UpdateDocPagina actualiza una página existente.
func UpdateDocPagina(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "ID inválido"})
}
type Req struct {
SaasID uint `json:"saas_id"`
CategoriaID uint `json:"categoria_id"`
Titulo string `json:"titulo"`
Slug string `json:"slug"`
Contenido string `json:"contenido"`
TipoContenido string `json:"tipo_contenido"`
Visibilidad string `json:"visibilidad"`
Orden int `json:"orden"`
Publicado bool `json:"publicado"`
RolIDs []uint `json:"rol_ids"`
}
var req Req
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Datos inválidos"})
}
item := &models.DocPagina{
SaasID: req.SaasID,
CategoriaID: req.CategoriaID,
Titulo: req.Titulo,
Slug: req.Slug,
Contenido: req.Contenido,
TipoContenido: req.TipoContenido,
Visibilidad: req.Visibilidad,
Orden: req.Orden,
Publicado: req.Publicado,
}
item.ID = uint(id)
if err := models.UpdateDocPagina(item, req.RolIDs); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"message": "Página actualizada"})
}
// DeleteDocPagina elimina una página de documentación.
func DeleteDocPagina(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "ID inválido"})
}
if err := models.DeleteDocPagina(uint(id)); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"message": "Página eliminada"})
}
// ─── Rutas públicas / privadas de lectura ─────────────────────────────────────
// DocsPublicoIndex renderiza el índice público de un SaaS (sin login).
func DocsPublicoIndex(c *fiber.Ctx) error {
saasSlug := c.Params("saas")
saas, err := models.GetSaasProductoBySlug(saasSlug)
if err != nil {
return c.Status(fiber.StatusNotFound).Render("errors/404", fiber.Map{}, "layouts/public")
}
paginas, err := models.GetDocPaginasPublicas(saas.ID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
// Agrupar por categoría
grupos := agruparPorCategoria(paginas)
return c.Render("docs/public-index", fiber.Map{
"saas": saas,
"grupos": grupos,
}, "layouts/public")
}
// DocsPublicaPagina renderiza una página pública individual.
func DocsPublicaPagina(c *fiber.Ctx) error {
saasSlug := c.Params("saas")
slug := c.Params("slug")
saas, err := models.GetSaasProductoBySlug(saasSlug)
if err != nil {
return c.Status(fiber.StatusNotFound).Render("errors/404", fiber.Map{}, "layouts/public")
}
pagina, err := models.GetDocPaginaBySlug(saas.ID, slug)
if err != nil || !pagina.Publicado {
return c.Status(fiber.StatusNotFound).Render("errors/404", fiber.Map{}, "layouts/public")
}
if pagina.Visibilidad == "private" {
return c.Status(fiber.StatusForbidden).Render("errors/403", fiber.Map{}, "layouts/public")
}
// Índice lateral: páginas públicas del mismo SaaS
todas, _ := models.GetDocPaginasPublicas(saas.ID)
grupos := agruparPorCategoria(todas)
return c.Render("docs/public-page", fiber.Map{
"saas": saas,
"pagina": pagina,
"grupos": grupos,
}, "layouts/public")
}
// DocsPrivadoIndex renderiza el índice de un SaaS para usuarios autenticados (incluye privadas con rol).
func DocsPrivadoIndex(c *fiber.Ctx) error {
saasSlug := c.Params("saas")
saas, err := models.GetSaasProductoBySlug(saasSlug)
if err != nil {
return c.Status(fiber.StatusNotFound).Render("errors/404", fiber.Map{}, "layouts/main")
}
rolNames := getUserRolNames(c)
paginas, err := models.GetDocPaginasParaRoles(saas.ID, rolNames)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
grupos := agruparPorCategoria(paginas)
return c.Render("docs/private-index", fiber.Map{
"user": c.Locals("user"),
"saas": saas,
"grupos": grupos,
}, "layouts/main")
}
// DocsPrivadaPagina renderiza una página con verificación de rol.
func DocsPrivadaPagina(c *fiber.Ctx) error {
saasSlug := c.Params("saas")
slug := c.Params("slug")
saas, err := models.GetSaasProductoBySlug(saasSlug)
if err != nil {
return c.Status(fiber.StatusNotFound).Render("errors/404", fiber.Map{}, "layouts/main")
}
pagina, err := models.GetDocPaginaBySlug(saas.ID, slug)
if err != nil || !pagina.Publicado {
return c.Status(fiber.StatusNotFound).Render("errors/404", fiber.Map{}, "layouts/main")
}
if pagina.Visibilidad == "private" {
// Verificar que el usuario tenga al menos un rol asignado a la página
rolNames := getUserRolNames(c)
if !paginaAccesible(pagina, rolNames) {
return c.Status(fiber.StatusForbidden).Render("errors/403", fiber.Map{
"user": c.Locals("user"),
}, "layouts/main")
}
}
rolNames := getUserRolNames(c)
todas, _ := models.GetDocPaginasParaRoles(saas.ID, rolNames)
grupos := agruparPorCategoria(todas)
return c.Render("docs/private-page", fiber.Map{
"user": c.Locals("user"),
"modules": c.Locals("userModules"),
"saas": saas,
"pagina": pagina,
"grupos": grupos,
}, "layouts/main")
}
// ─── helpers ──────────────────────────────────────────────────────────────────
type GrupoCategoria struct {
Categoria models.DocCategoria
Paginas []models.DocPagina
}
func agruparPorCategoria(paginas []models.DocPagina) []GrupoCategoria {
orden := []uint{}
mapa := map[uint]*GrupoCategoria{}
for _, p := range paginas {
if _, ok := mapa[p.CategoriaID]; !ok {
orden = append(orden, p.CategoriaID)
mapa[p.CategoriaID] = &GrupoCategoria{Categoria: p.Categoria}
}
mapa[p.CategoriaID].Paginas = append(mapa[p.CategoriaID].Paginas, p)
}
var result []GrupoCategoria
for _, id := range orden {
result = append(result, *mapa[id])
}
return result
}
func getUserRolNames(c *fiber.Ctx) []string {
userMap, ok := c.Locals("user").(map[string]interface{})
if !ok {
return nil
}
raw, ok := userMap["roles"]
if !ok {
return nil
}
var names []string
switch v := raw.(type) {
case []string:
names = v
case []interface{}:
for _, r := range v {
if s, ok := r.(string); ok {
names = append(names, s)
}
}
}
return names
}
func paginaAccesible(p *models.DocPagina, userRoles []string) bool {
if p.Visibilidad == "public" {
return true
}
for _, pr := range p.Roles {
for _, ur := range userRoles {
if pr.Name == ur {
return true
}
}
}
return false
}