This commit is contained in:
Lizandro Guarnizo
2026-05-16 20:01:36 -05:00
parent 4c14f4a641
commit 2b7458b612
8 changed files with 972 additions and 0 deletions
@@ -0,0 +1,285 @@
package controllers
import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/gofiber/fiber/v2"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
)
// ─── Admin: vista principal ───────────────────────────────────────────────────
func PartnerRecursosIndex(c *fiber.Ctx) error {
return c.Render("partner_recursos", fiber.Map{
"user": c.Locals("user"),
"modules": c.Locals("userModules"),
}, "layouts/main")
}
// ─── Recursos ─────────────────────────────────────────────────────────────────
func LoadPartnerRecursos(c *fiber.Ctx) error {
recursos, err := models.GetAllPartnerRecursos(false)
if err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"recursos": recursos})
}
func CreatePartnerRecurso(c *fiber.Ctx) error {
type Req struct {
Nombre string `json:"nombre"`
Descripcion string `json:"descripcion"`
Categoria string `json:"categoria"`
Activo bool `json:"activo"`
}
var req Req
if err := c.BodyParser(&req); err != nil {
return c.Status(400).JSON(fiber.Map{"error": "Datos inválidos"})
}
r := &models.PartnerRecurso{
Nombre: req.Nombre,
Descripcion: req.Descripcion,
Categoria: req.Categoria,
Activo: req.Activo,
}
if err := models.CreatePartnerRecurso(r); err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
}
return c.Status(201).JSON(r)
}
func UpdatePartnerRecurso(c *fiber.Ctx) error {
id, _ := strconv.ParseUint(c.Params("id"), 10, 32)
type Req struct {
Nombre string `json:"nombre"`
Descripcion string `json:"descripcion"`
Categoria string `json:"categoria"`
Activo bool `json:"activo"`
}
var req Req
if err := c.BodyParser(&req); err != nil {
return c.Status(400).JSON(fiber.Map{"error": "Datos inválidos"})
}
r, err := models.GetPartnerRecursoByID(uint(id))
if err != nil {
return c.Status(404).JSON(fiber.Map{"error": "No encontrado"})
}
r.Nombre = req.Nombre
r.Descripcion = req.Descripcion
r.Categoria = req.Categoria
r.Activo = req.Activo
if err := models.UpdatePartnerRecurso(r); err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(r)
}
func DeletePartnerRecurso(c *fiber.Ctx) error {
id, _ := strconv.ParseUint(c.Params("id"), 10, 32)
if err := models.DeletePartnerRecurso(uint(id)); err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"ok": true})
}
func UploadPartnerRecursoArchivo(c *fiber.Ctx) error {
id, _ := strconv.ParseUint(c.Params("id"), 10, 32)
file, err := c.FormFile("archivo")
if err != nil {
return c.Status(400).JSON(fiber.Map{"error": "Archivo requerido"})
}
if file.Size > 50*1024*1024 {
return c.Status(400).JSON(fiber.Map{"error": "Máximo 50MB"})
}
dir := "uploads/partner_recursos"
if err := os.MkdirAll(dir, 0755); err != nil {
return c.Status(500).JSON(fiber.Map{"error": "Error al crear directorio"})
}
safeFile := safeFilenameProyecto(file.Filename)
savePath := filepath.Join(dir, fmt.Sprintf("%d_%s", id, safeFile))
// Validar que la ruta quede dentro de uploads/
clean := filepath.Clean(savePath)
if !strings.HasPrefix(clean, "uploads/") {
return c.Status(400).JSON(fiber.Map{"error": "Ruta inválida"})
}
if err := c.SaveFile(file, savePath); err != nil {
return c.Status(500).JSON(fiber.Map{"error": "Error al guardar archivo"})
}
r, err := models.GetPartnerRecursoByID(uint(id))
if err != nil {
return c.Status(404).JSON(fiber.Map{"error": "Recurso no encontrado"})
}
r.Archivo = savePath
r.NombreOrig = file.Filename
if err := models.UpdatePartnerRecurso(r); err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"ok": true, "archivo": savePath, "nombre_orig": file.Filename})
}
func DownloadPartnerRecurso(c *fiber.Ctx) error {
id, _ := strconv.ParseUint(c.Params("id"), 10, 32)
r, err := models.GetPartnerRecursoByID(uint(id))
if err != nil || r.Archivo == "" {
return c.Status(404).JSON(fiber.Map{"error": "Archivo no encontrado"})
}
clean := filepath.Clean(r.Archivo)
if !strings.HasPrefix(clean, "uploads/") {
return c.Status(403).JSON(fiber.Map{"error": "Acceso denegado"})
}
nombre := r.NombreOrig
if nombre == "" {
nombre = filepath.Base(r.Archivo)
}
c.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, nombre))
return c.SendFile(clean)
}
// ─── Comunicados ──────────────────────────────────────────────────────────────
func LoadPartnerComunicados(c *fiber.Ctx) error {
items, err := models.GetAllPartnerComunicados(false)
if err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"comunicados": items})
}
func CreatePartnerComunicado(c *fiber.Ctx) error {
type Req struct {
Titulo string `json:"titulo"`
Contenido string `json:"contenido"`
Activo bool `json:"activo"`
}
var req Req
if err := c.BodyParser(&req); err != nil {
return c.Status(400).JSON(fiber.Map{"error": "Datos inválidos"})
}
item := &models.PartnerComunicado{
Titulo: req.Titulo,
Contenido: req.Contenido,
Activo: req.Activo,
}
if err := models.CreatePartnerComunicado(item); err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
}
return c.Status(201).JSON(item)
}
func UpdatePartnerComunicado(c *fiber.Ctx) error {
id, _ := strconv.ParseUint(c.Params("id"), 10, 32)
type Req struct {
Titulo string `json:"titulo"`
Contenido string `json:"contenido"`
Activo bool `json:"activo"`
}
var req Req
if err := c.BodyParser(&req); err != nil {
return c.Status(400).JSON(fiber.Map{"error": "Datos inválidos"})
}
item, err := models.GetPartnerComunicadoByID(uint(id))
if err != nil {
return c.Status(404).JSON(fiber.Map{"error": "No encontrado"})
}
item.Titulo = req.Titulo
item.Contenido = req.Contenido
item.Activo = req.Activo
if err := models.UpdatePartnerComunicado(item); err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(item)
}
func DeletePartnerComunicado(c *fiber.Ctx) error {
id, _ := strconv.ParseUint(c.Params("id"), 10, 32)
if err := models.DeletePartnerComunicado(uint(id)); err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"ok": true})
}
func UploadPartnerComunicadoArchivo(c *fiber.Ctx) error {
id, _ := strconv.ParseUint(c.Params("id"), 10, 32)
file, err := c.FormFile("archivo")
if err != nil {
return c.Status(400).JSON(fiber.Map{"error": "Archivo requerido"})
}
if file.Size > 50*1024*1024 {
return c.Status(400).JSON(fiber.Map{"error": "Máximo 50MB"})
}
dir := "uploads/partner_comunicados"
if err := os.MkdirAll(dir, 0755); err != nil {
return c.Status(500).JSON(fiber.Map{"error": "Error al crear directorio"})
}
safeFile := safeFilenameProyecto(file.Filename)
savePath := filepath.Join(dir, fmt.Sprintf("%d_%s", id, safeFile))
clean := filepath.Clean(savePath)
if !strings.HasPrefix(clean, "uploads/") {
return c.Status(400).JSON(fiber.Map{"error": "Ruta inválida"})
}
if err := c.SaveFile(file, savePath); err != nil {
return c.Status(500).JSON(fiber.Map{"error": "Error al guardar archivo"})
}
item, err := models.GetPartnerComunicadoByID(uint(id))
if err != nil {
return c.Status(404).JSON(fiber.Map{"error": "Comunicado no encontrado"})
}
item.Archivo = savePath
item.NombreOrig = file.Filename
if err := models.UpdatePartnerComunicado(item); err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"ok": true, "archivo": savePath, "nombre_orig": file.Filename})
}
func DownloadPartnerComunicadoArchivo(c *fiber.Ctx) error {
id, _ := strconv.ParseUint(c.Params("id"), 10, 32)
item, err := models.GetPartnerComunicadoByID(uint(id))
if err != nil || item.Archivo == "" {
return c.Status(404).JSON(fiber.Map{"error": "Archivo no encontrado"})
}
clean := filepath.Clean(item.Archivo)
if !strings.HasPrefix(clean, "uploads/") {
return c.Status(403).JSON(fiber.Map{"error": "Acceso denegado"})
}
nombre := item.NombreOrig
if nombre == "" {
nombre = filepath.Base(item.Archivo)
}
c.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, nombre))
return c.SendFile(clean)
}
// ─── Portal: acceso para partners ────────────────────────────────────────────
func PortalPartnerRecursos(c *fiber.Ctx) error {
recursos, _ := models.GetAllPartnerRecursos(true)
comunicados, _ := models.GetAllPartnerComunicados(true)
return c.JSON(fiber.Map{
"recursos": recursos,
"comunicados": comunicados,
})
}
func PortalDownloadPartnerRecurso(c *fiber.Ctx) error {
id, _ := strconv.ParseUint(c.Params("id"), 10, 32)
r, err := models.GetPartnerRecursoByID(uint(id))
if err != nil || r.Archivo == "" || !r.Activo {
return c.Status(404).JSON(fiber.Map{"error": "Archivo no encontrado"})
}
clean := filepath.Clean(r.Archivo)
if !strings.HasPrefix(clean, "uploads/") {
return c.Status(403).JSON(fiber.Map{"error": "Acceso denegado"})
}
nombre := r.NombreOrig
if nombre == "" {
nombre = filepath.Base(r.Archivo)
}
c.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, nombre))
return c.SendFile(clean)
}
+11
View File
@@ -35,3 +35,14 @@ func PortalUserFromLocals(c *fiber.Ctx) *models.PortalUser {
u, _ := c.Locals("portalUser").(*models.PortalUser)
return u
}
// PortalPartnerOnly permite el acceso solo a usuarios con rol "partner".
func PortalPartnerOnly() fiber.Handler {
return func(c *fiber.Ctx) error {
u := PortalUserFromLocals(c)
if u == nil || u.Rol != "partner" {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "Acceso exclusivo para partners"})
}
return c.Next()
}
}
+5
View File
@@ -35,4 +35,9 @@ func PortalRoutes(app fiber.Router) {
portal.Get("/mis-notifs", controllers.PortalGetMisNotifs)
portal.Put("/mis-notifs/:id/leida", controllers.PortalMarcarNotifLeida)
portal.Post("/mis-notifs/marcar-todas", controllers.PortalMarcarTodasLeidas)
// 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)
}
+16
View File
@@ -295,6 +295,22 @@ func UserRoutes(app fiber.Router) {
protected.Get("/notif-config/data", controllers.GetNotifConfigs)
protected.Post("/notif-config", controllers.SaveNotifConfig)
// Partner Recursos (documentación y comunicados para partners)
protected.Get("/partner-recursos", middlewares.MenuMiddleware, controllers.PartnerRecursosIndex)
protected.Get("/partner-recursos/data", controllers.LoadPartnerRecursos)
protected.Post("/partner-recursos", controllers.CreatePartnerRecurso)
protected.Put("/partner-recursos/:id", controllers.UpdatePartnerRecurso)
protected.Delete("/partner-recursos/:id", controllers.DeletePartnerRecurso)
protected.Post("/partner-recursos/:id/upload", controllers.UploadPartnerRecursoArchivo)
protected.Get("/partner-recursos/:id/download", controllers.DownloadPartnerRecurso)
protected.Get("/partner-comunicados/data", controllers.LoadPartnerComunicados)
protected.Post("/partner-comunicados", controllers.CreatePartnerComunicado)
protected.Put("/partner-comunicados/:id", controllers.UpdatePartnerComunicado)
protected.Delete("/partner-comunicados/:id", controllers.DeletePartnerComunicado)
protected.Post("/partner-comunicados/:id/upload", controllers.UploadPartnerComunicadoArchivo)
protected.Get("/partner-comunicados/:id/download", controllers.DownloadPartnerComunicadoArchivo)
// Notificaciones in-app del admin (bell)
protected.Get("/mis-notifs", controllers.GetMisNotifs)
protected.Put("/mis-notifs/:id/leida", controllers.MarcarNotifLeida)