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 }