documentacion

This commit is contained in:
Lizandro Guarnizo
2026-05-06 14:58:55 -05:00
parent 7f3fdfb4be
commit 6bf4b32a18
16 changed files with 2025 additions and 1 deletions
+67
View File
@@ -0,0 +1,67 @@
package models
import (
"github.com/sujit-baniya/fiber-boilerplate/app"
"gorm.io/gorm"
)
// DocCategoria es una categoría GLOBAL que puede aplicarse a la documentación de cualquier SaaS.
// Ejemplos: "Primeros Pasos", "API Reference", "Configuración", "Facturación".
type DocCategoria struct {
gorm.Model
Nombre string `json:"nombre" gorm:"column:nombre;not null"`
Slug string `json:"slug" gorm:"column:slug;uniqueIndex;not null"`
Descripcion string `json:"descripcion" gorm:"column:descripcion;type:text"`
Orden int `json:"orden" gorm:"column:orden;default:0"`
}
func (DocCategoria) TableName() string { return "doc_categorias" }
func GetAllDocCategorias(limit, offset int, search string) ([]DocCategoria, int64, error) {
var items []DocCategoria
var total int64
db := app.Http.Database.DB.Model(&DocCategoria{})
if search != "" {
db = db.Where("nombre ILIKE ? OR descripcion ILIKE ?", "%"+search+"%", "%"+search+"%")
}
if err := db.Count(&total).Error; err != nil {
return nil, 0, err
}
if err := db.Order("orden ASC, nombre ASC").Limit(limit).Offset(offset).Find(&items).Error; err != nil {
return nil, 0, err
}
return items, total, nil
}
func GetAllDocCategoriasSelect() ([]DocCategoria, error) {
var items []DocCategoria
if err := app.Http.Database.DB.Order("orden ASC, nombre ASC").Find(&items).Error; err != nil {
return nil, err
}
return items, nil
}
func GetDocCategoriaByID(id uint) (*DocCategoria, error) {
var item DocCategoria
if err := app.Http.Database.DB.First(&item, id).Error; err != nil {
return nil, err
}
return &item, nil
}
func CreateDocCategoria(c *DocCategoria) error {
return app.Http.Database.DB.Create(c).Error
}
func UpdateDocCategoria(c *DocCategoria) error {
return app.Http.Database.DB.Model(&DocCategoria{}).Where("id = ?", c.ID).Updates(map[string]interface{}{
"nombre": c.Nombre,
"slug": c.Slug,
"descripcion": c.Descripcion,
"orden": c.Orden,
}).Error
}
func DeleteDocCategoria(id uint) error {
return app.Http.Database.DB.Delete(&DocCategoria{}, id).Error
}
+166
View File
@@ -0,0 +1,166 @@
package models
import (
"github.com/sujit-baniya/fiber-boilerplate/app"
"gorm.io/gorm"
)
// DocPagina es el contenido real de documentación.
// Visibilidad "public" → accesible sin login.
// Visibilidad "private" → requiere login Y tener al menos uno de los roles asignados.
// TipoContenido "markdown" | "html" determina cómo se renderiza el campo Contenido.
type DocPagina struct {
gorm.Model
SaasID uint `json:"saas_id" gorm:"column:saas_id;not null"`
Saas SaasProducto `json:"saas" gorm:"foreignKey:SaasID"`
CategoriaID uint `json:"categoria_id" gorm:"column:categoria_id;not null"`
Categoria DocCategoria `json:"categoria" gorm:"foreignKey:CategoriaID"`
Titulo string `json:"titulo" gorm:"column:titulo;not null"`
Slug string `json:"slug" gorm:"column:slug;not null;uniqueIndex:idx_saas_slug"`
Contenido string `json:"contenido" gorm:"column:contenido;type:text"`
TipoContenido string `json:"tipo_contenido" gorm:"column:tipo_contenido;default:'markdown'"` // markdown | html
Visibilidad string `json:"visibilidad" gorm:"column:visibilidad;default:'public'"` // public | private
Orden int `json:"orden" gorm:"column:orden;default:0"`
Publicado bool `json:"publicado" gorm:"column:publicado;default:false"`
CreadoPor uint `json:"creado_por" gorm:"column:creado_por"`
// Roles que pueden ver esta página cuando visibilidad = "private"
Roles []Roles `json:"roles" gorm:"many2many:doc_pagina_roles"`
}
func (DocPagina) TableName() string { return "doc_paginas" }
// ─── Queries ──────────────────────────────────────────────────────────────────
func GetAllDocPaginas(limit, offset int, search string, saasID, categoriaID uint) ([]DocPagina, int64, error) {
var items []DocPagina
var total int64
db := app.Http.Database.DB.Model(&DocPagina{}).
Preload("Saas").Preload("Categoria").Preload("Roles")
if search != "" {
db = db.Where("titulo ILIKE ?", "%"+search+"%")
}
if saasID > 0 {
db = db.Where("saas_id = ?", saasID)
}
if categoriaID > 0 {
db = db.Where("categoria_id = ?", categoriaID)
}
if err := db.Count(&total).Error; err != nil {
return nil, 0, err
}
if err := db.Order("saas_id ASC, orden ASC, created_at DESC").Limit(limit).Offset(offset).Find(&items).Error; err != nil {
return nil, 0, err
}
return items, total, nil
}
// GetDocPaginasPublicas devuelve páginas publicadas y públicas de un SaaS, agrupables por categoría.
func GetDocPaginasPublicas(saasID uint) ([]DocPagina, error) {
var items []DocPagina
if err := app.Http.Database.DB.
Preload("Categoria").
Where("saas_id = ? AND visibilidad = 'public' AND publicado = true", saasID).
Order("orden ASC, titulo ASC").
Find(&items).Error; err != nil {
return nil, err
}
return items, nil
}
// GetDocPaginasParaRoles devuelve páginas publicadas accesibles por los roles dados (incluye public + private con rol).
func GetDocPaginasParaRoles(saasID uint, rolNames []string) ([]DocPagina, error) {
var items []DocPagina
db := app.Http.Database.DB.Preload("Categoria").Preload("Roles")
// Páginas públicas del SaaS
if err := db.Where("saas_id = ? AND publicado = true AND visibilidad = 'public'", saasID).
Order("orden ASC, titulo ASC").Find(&items).Error; err != nil {
return nil, err
}
// Páginas privadas accesibles por los roles del usuario
if len(rolNames) > 0 {
var privadas []DocPagina
if err := app.Http.Database.DB.Preload("Categoria").Preload("Roles").
Joins("JOIN doc_pagina_roles dpr ON dpr.doc_pagina_id = doc_paginas.id").
Joins("JOIN roles r ON r.id = dpr.roles_id").
Where("doc_paginas.saas_id = ? AND doc_paginas.publicado = true AND doc_paginas.visibilidad = 'private' AND r.name IN ?", saasID, rolNames).
Group("doc_paginas.id").
Order("doc_paginas.orden ASC, doc_paginas.titulo ASC").
Find(&privadas).Error; err != nil {
return nil, err
}
items = append(items, privadas...)
}
return items, nil
}
func GetDocPaginaByID(id uint) (*DocPagina, error) {
var item DocPagina
if err := app.Http.Database.DB.Preload("Saas").Preload("Categoria").Preload("Roles").First(&item, id).Error; err != nil {
return nil, err
}
return &item, nil
}
func GetDocPaginaBySlug(saasID uint, slug string) (*DocPagina, error) {
var item DocPagina
if err := app.Http.Database.DB.Preload("Saas").Preload("Categoria").Preload("Roles").
Where("saas_id = ? AND slug = ?", saasID, slug).
First(&item).Error; err != nil {
return nil, err
}
return &item, nil
}
func CreateDocPagina(p *DocPagina, rolIDs []uint) error {
db := app.Http.Database.DB
if err := db.Omit("Roles.*").Create(p).Error; err != nil {
return err
}
return syncDocPaginaRoles(p.ID, rolIDs)
}
func UpdateDocPagina(p *DocPagina, rolIDs []uint) error {
db := app.Http.Database.DB
if err := db.Model(&DocPagina{}).Where("id = ?", p.ID).Updates(map[string]interface{}{
"saas_id": p.SaasID,
"categoria_id": p.CategoriaID,
"titulo": p.Titulo,
"slug": p.Slug,
"contenido": p.Contenido,
"tipo_contenido": p.TipoContenido,
"visibilidad": p.Visibilidad,
"orden": p.Orden,
"publicado": p.Publicado,
}).Error; err != nil {
return err
}
return syncDocPaginaRoles(p.ID, rolIDs)
}
func DeleteDocPagina(id uint) error {
db := app.Http.Database.DB
// Limpiar la tabla join antes de borrar el registro
if err := db.Exec("DELETE FROM doc_pagina_roles WHERE doc_pagina_id = ?", id).Error; err != nil {
return err
}
return db.Delete(&DocPagina{}, id).Error
}
// syncDocPaginaRoles sincroniza los roles de una página usando SQL directo.
func syncDocPaginaRoles(paginaID uint, rolIDs []uint) error {
db := app.Http.Database.DB
if err := db.Exec("DELETE FROM doc_pagina_roles WHERE doc_pagina_id = ?", paginaID).Error; err != nil {
return err
}
for _, rid := range rolIDs {
if err := db.Exec(
"INSERT INTO doc_pagina_roles (doc_pagina_id, roles_id) VALUES (?, ?) ON CONFLICT DO NOTHING",
paginaID, rid,
).Error; err != nil {
return err
}
}
return nil
}
+82
View File
@@ -0,0 +1,82 @@
package models
import (
"github.com/sujit-baniya/fiber-boilerplate/app"
"gorm.io/gorm"
)
// SaasProducto representa un producto SaaS al que se le puede asociar documentación.
// servicio_id es opcional: un SaaS puede estar relacionado con un servicio existente o ser independiente.
type SaasProducto struct {
gorm.Model
Nombre string `json:"nombre" gorm:"column:nombre;not null"`
Slug string `json:"slug" gorm:"column:slug;uniqueIndex;not null"`
Descripcion string `json:"descripcion" gorm:"column:descripcion;type:text"`
LogoURL string `json:"logo_url" gorm:"column:logo_url"`
ServicioID *uint `json:"servicio_id" gorm:"column:servicio_id"` // nullable FK
Servicio *Servicio `json:"servicio" gorm:"foreignKey:ServicioID"`
Activo bool `json:"activo" gorm:"column:activo;default:true"`
Orden int `json:"orden" gorm:"column:orden;default:0"`
}
func (SaasProducto) TableName() string { return "saas_productos" }
func GetAllSaasProductos(limit, offset int, search string) ([]SaasProducto, int64, error) {
var items []SaasProducto
var total int64
db := app.Http.Database.DB.Model(&SaasProducto{}).Preload("Servicio")
if search != "" {
db = db.Where("nombre ILIKE ? OR descripcion ILIKE ?", "%"+search+"%", "%"+search+"%")
}
if err := db.Count(&total).Error; err != nil {
return nil, 0, err
}
if err := db.Order("orden ASC, created_at DESC").Limit(limit).Offset(offset).Find(&items).Error; err != nil {
return nil, 0, err
}
return items, total, nil
}
func GetAllSaasProductosSelect() ([]SaasProducto, error) {
var items []SaasProducto
if err := app.Http.Database.DB.Where("activo = ?", true).Order("orden ASC, nombre ASC").Find(&items).Error; err != nil {
return nil, err
}
return items, nil
}
func GetSaasProductoByID(id uint) (*SaasProducto, error) {
var item SaasProducto
if err := app.Http.Database.DB.Preload("Servicio").First(&item, id).Error; err != nil {
return nil, err
}
return &item, nil
}
func GetSaasProductoBySlug(slug string) (*SaasProducto, error) {
var item SaasProducto
if err := app.Http.Database.DB.Where("slug = ?", slug).First(&item).Error; err != nil {
return nil, err
}
return &item, nil
}
func CreateSaasProducto(s *SaasProducto) error {
return app.Http.Database.DB.Create(s).Error
}
func UpdateSaasProducto(s *SaasProducto) error {
return app.Http.Database.DB.Model(&SaasProducto{}).Where("id = ?", s.ID).Updates(map[string]interface{}{
"nombre": s.Nombre,
"slug": s.Slug,
"descripcion": s.Descripcion,
"logo_url": s.LogoURL,
"servicio_id": s.ServicioID,
"activo": s.Activo,
"orden": s.Orden,
}).Error
}
func DeleteSaasProducto(id uint) error {
return app.Http.Database.DB.Delete(&SaasProducto{}, id).Error
}