Initial commit

This commit is contained in:
Lizandro Guarnizo
2025-02-06 14:22:29 -05:00
commit 6d8f1bcd6f
452 changed files with 250835 additions and 0 deletions
+96
View File
@@ -0,0 +1,96 @@
package controllers
import (
"github.com/gofiber/fiber/v2" //nolint:goimports
"github.com/gookit/validate"
"github.com/sujit-baniya/fiber-boilerplate/app"
"github.com/sujit-baniya/fiber-boilerplate/pkg/auth"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
"github.com/sujit-baniya/fiber-boilerplate/pkg/services"
)
func OAuthToken(c *fiber.Ctx) error { //nolint:wsl
var login models.Login
if err := c.BodyParser(&login); err != nil {
return c.Status(401).JSON(fiber.Map{
"error": true,
"message": "Invalid Credentials",
})
}
v := validate.Struct(login)
if !v.Validate() {
return c.Status(401).JSON(fiber.Map{
"error": true,
"message": v.Errors.All(),
})
}
user, err := login.CheckLogin() //nolint:wsl
if err != nil {
return c.Status(401).JSON(fiber.Map{
"error": true,
"message": err.Error(),
})
}
token, err := auth.Login(c, user.ID, app.Http.Token.ApiJwtSecret) //nolint:wsl
if err != nil {
return c.Status(401).JSON(fiber.Map{
"error": true,
"message": err.Error(),
})
}
return c.JSON(fiber.Map{
"token": token.Hash,
"expires_in": token.Expire,
})
}
func ApiLoginPost(c *fiber.Ctx) error { //nolint:wsl
// Obtener el usuario del contexto
user, ok := c.Locals("user").(*models.User)
if !ok || user == nil {
return c.JSON(fiber.Map{
"success": false,
"message": "Usuario no encontrado o no autenticado",
})
}
// Generar el token JWT para el usuario
token, err := auth.Login(c, user.ID, app.Http.Token.AppJwtSecret)
if err != nil {
return c.JSON(fiber.Map{
"success": false,
"message": "Error al generar el token: " + err.Error(),
})
}
// Retornar respuesta con el usuario y el token
return c.JSON(fiber.Map{
"success": true,
"message": "Inicio de sesión exitoso",
"user": user,
"token": token,
})
}
func ApiRegisterPost(c *fiber.Ctx) error {
register := c.Locals("register").(models.RegisterForm)
user, err := register.Signup()
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": true, "message": "Error on register request", "data": err.Error()}) //nolint:errcheck
}
store := app.Http.Session.Get(c) // get/create new session
store.Set("user_id", user.ID) // save to storage
_ = store.Save()
go services.SendConfirmationEmail(user.Email, c.BaseURL(), user.NombreUsuario)
return c.JSON(fiber.Map{
"success": true,
"message": "Registered successfully! Please confirm your email",
})
}
func RefreshOauthToken(c *fiber.Ctx) error {
return nil
}
+7
View File
@@ -0,0 +1,7 @@
package controllers
import "github.com/gofiber/fiber/v2"
func Me(c *fiber.Ctx) error {
return nil
}
+105
View File
@@ -0,0 +1,105 @@
package controllers
import (
"time"
"github.com/gofiber/fiber/v2" //nolint:goimports
"github.com/gofiber/fiber/v2/middleware/session"
"github.com/sujit-baniya/fiber-boilerplate/app"
"github.com/sujit-baniya/fiber-boilerplate/pkg/auth"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
)
func LoginGet(c *fiber.Ctx) error {
data := getFlashData(c)
data["title"] = "Login | "
return c.Render("auth/login", data, "layouts/landing")
}
func LoginPost(c *fiber.Ctx) error {
// Verificar si falta la contraseña
if c.Locals("missing_password") == true {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"status": "missing",
"message": "no_password",
})
}
// Obtener el usuario desde el contexto
user := c.Locals("user").(*models.Users)
// Intentar realizar el login
_, err := auth.Login(c, user.ID, app.Http.Token.AppJwtSecret) //nolint:wsl
if err != nil {
// Si ocurre un error, devolverlo en formato JSON
return c.JSON(fiber.Map{
"status": "error",
"message": err.Error(),
})
}
// Si todo está bien, devolver éxito y el token JWT
return c.JSON(fiber.Map{
"status": "success",
"message": "Login exitoso",
})
}
var store = session.New()
func LogoutPost(c *fiber.Ctx) error {
// Verifica si el usuario está autenticado
if auth.IsLoggedIn(c) {
// Cierra la sesión en el backend
err := auth.Logout(c)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Logout failed")
}
}
// Elimina la sesión actual
sess, err := store.Get(c)
if err == nil {
_ = sess.Destroy()
}
// Limpia ambas cookies de sesión
c.ClearCookie("session_id")
c.ClearCookie("Verify-Rest-Token")
// Limpia la cookie CSRF
c.Cookie(&fiber.Cookie{
Name: "Verify-Rest-Token", // Nombre de la cookie CSRF, ajusta si usas un nombre diferente
Value: "",
Expires: time.Now().Add(-1 * time.Hour), // Establece la cookie para que expire en el pasado
HTTPOnly: true,
Secure: true,
SameSite: "Strict",
})
// Limpia la cookie CSRF
c.Cookie(&fiber.Cookie{
Name: "session_id", // Nombre de la cookie CSRF, ajusta si usas un nombre diferente
Value: "",
Expires: time.Now().Add(-1 * time.Hour), // Establece la cookie para que expire en el pasado
HTTPOnly: true,
Secure: true,
SameSite: "Strict",
})
// Ajusta encabezados de caché para evitar almacenamientos no deseados en caché
c.Set("X-DNS-Prefetch-Control", "off")
c.Set("Pragma", "no-cache")
c.Set("Expires", "Fri, 01 Jan 1990 00:00:00 GMT")
c.Set("Cache-Control", "no-cache, must-revalidate, no-store, max-age=0, private")
// Redirige al usuario a la página de inicio de sesión
return c.Redirect("/login")
}
func getFlashData(c *fiber.Ctx) fiber.Map {
data := app.Http.Flash.Get(c)
if data == nil {
data = fiber.Map{}
}
return data
}
+22
View File
@@ -0,0 +1,22 @@
package controllers
import (
"github.com/gofiber/fiber/v2"
)
// RENDER
func Dashboard(c *fiber.Ctx) error {
data := fiber.Map{
"user": c.Locals("user").(map[string]interface{}),
"modules": c.Locals("userModules"),
}
// Renderiza la vista con el layout layouts/main
if err := c.Render("dashboard", data, "layouts/main"); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error rendering template",
"error": err.Error(),
})
}
return nil
}
+75
View File
@@ -0,0 +1,75 @@
package controllers
import (
"github.com/gofiber/fiber/v2"
"github.com/sujit-baniya/fiber-boilerplate/pkg/auth"
)
func Landing(c *fiber.Ctx) error {
user, err := auth.User(c)
if err != nil {
auth.Logout(c)
}
view := "index"
if err := c.Render(view, fiber.Map{
"auth": user != nil,
"user": user,
}, "layouts/main"); err != nil {
panic(err.Error())
}
return nil
}
func Terms(c *fiber.Ctx) error {
if err := c.Render("terms", fiber.Map{"title": "Terms | "}, "layouts/landing"); err != nil {
panic(err.Error())
}
return nil
}
func PrivacyPolicy(c *fiber.Ctx) error {
if err := c.Render("privacy-policy", fiber.Map{"title": "Privacy Policy | "}, "layouts/landing"); err != nil {
panic(err.Error())
}
return nil
}
func Disclaimer(c *fiber.Ctx) error {
if err := c.Render("disclaimer", fiber.Map{"title": "Disclaimer | "}, "layouts/main"); err != nil {
panic(err.Error())
}
return nil
}
func App(c *fiber.Ctx) error {
user, err := auth.User(c)
if err != nil {
auth.Logout(c)
}
view := "home"
if err := c.Render(view, fiber.Map{
"auth": user != nil,
"user": user,
}); err != nil {
panic(err.Error())
}
return nil
}
func Admin(c *fiber.Ctx) error {
user, err := auth.User(c)
if err != nil {
auth.Logout(c)
}
view := "admin-home"
if err := c.Render(view, fiber.Map{
"auth": user != nil,
"user": user,
}); err != nil {
panic(err.Error())
}
return nil
}
+162
View File
@@ -0,0 +1,162 @@
package controllers
import (
"math"
"strconv"
"github.com/gofiber/fiber/v2" //nolint:goimports
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
)
// RENDER
func Modules(c *fiber.Ctx) error {
// Renderiza la vista "modules" con el layout "layouts/landing"
// Renderizar la vista "roles" y pasar los datos al frontend
data := fiber.Map{
"user": c.Locals("user").(map[string]interface{}),
"modules": c.Locals("userModules"),
}
if err := c.Render("modules", data, "layouts/main"); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error rendering template",
"error": err.Error(),
})
}
return nil
}
//CONSULTAR TODOS LOS MODULOS
func GetModules(c *fiber.Ctx) error {
// Obtener parámetros de consulta para la paginación
pageStr := c.Query("page", "1") // Por defecto, la página es 1
page, err := strconv.Atoi(pageStr)
if err != nil || page < 1 {
page = 1
}
searchQuery := c.Query("search", "") // Obtener el término de búsqueda
limit := 10 // Número de módulos por página
offset := (page - 1) * limit
// Llama a AllModules con el término de búsqueda
modules, total, err := models.AllModules(limit, offset, searchQuery)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error retrieving modules",
"error": err.Error(),
})
}
// Calcular el total de páginas
totalPages := int(math.Ceil(float64(total) / float64(limit)))
// Envía los módulos como respuesta JSON junto con la información de paginación
return c.JSON(fiber.Map{
"modules": modules,
"total": total, // Total de módulos disponibles
"totalPages": totalPages, // Total de páginas
"page": page, // Página actual
"limit": limit, // Límites por página
})
}
func AllModulesSelect(c *fiber.Ctx) error {
modules, err := models.AllModulesWithSubmodules()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error retrieving modules",
"error": err.Error(),
})
}
return c.JSON(fiber.Map{
"modules": modules,
})
}
// ACTUALIZAR
func UpdateModule(c *fiber.Ctx) error {
var m models.Modules
uid, err := strconv.ParseUint(c.Params("id"), 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "ID inválido",
"error": true,
})
}
// Analiza el cuerpo de la solicitud
if err := c.BodyParser(&m); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Error al analizar el cuerpo de la solicitud",
"error": true,
})
}
m.ID = uint(uid)
// Actualiza el módulo en la base de datos
if err := models.UpdateModule(m); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": err.Error(),
"error": true,
})
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"message": "Módulo actualizado con éxito",
"error": false,
"module": m,
})
}
// CREAR
func CreateModule(c *fiber.Ctx) error {
var m models.Modules
// Analiza el cuerpo de la solicitud
if err := c.BodyParser(&m); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Error al analizar el cuerpo de la solicitud",
"error": true,
})
}
// Crea el nuevo módulo en la base de datos
if err := models.CreateModule(m); err != nil { // Asegúrate de tener una función para crear un módulo
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": err.Error(),
"error": true,
})
}
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
"message": "Módulo creado con éxito",
"error": false,
"module": m,
})
}
// ELIMINAR
func DeleteModule(c *fiber.Ctx) error {
uid, err := strconv.ParseUint(c.Params("id"), 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Invalid ID",
"error": true,
})
}
if err := models.DeleteModule(uint(uid)); err != nil { // Ensure you have a function for deleting a module
return c.JSON(fiber.Map{
"message": err.Error(),
"error": true,
})
}
return c.JSON(fiber.Map{
"message": "Modulo eliminado correctamente",
"error": false,
})
}
+27
View File
@@ -0,0 +1,27 @@
package controllers
import (
"github.com/gofiber/fiber/v2"
)
func Profile(c *fiber.Ctx) error {
// Obtiene el usuario del contexto en formato map[string]interface{}
user, ok := c.Locals("user").(map[string]interface{})
if !ok || user == nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"message": "Usuario no autenticado",
})
}
// Obtener el usuario del contexto
// Renderiza la vista "profile" pasando el usuario
if err := c.Render("profile", fiber.Map{
"user": user,
}, "layouts/main"); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error rendering template",
"error": err.Error(),
})
}
return nil
}
+302
View File
@@ -0,0 +1,302 @@
package controllers
import (
"crypto/rand"
"errors"
"fmt"
"log"
"math/big"
"strings"
"github.com/gofiber/fiber/v2"
"github.com/sujit-baniya/fiber-boilerplate/app"
"github.com/sujit-baniya/fiber-boilerplate/config"
"github.com/sujit-baniya/fiber-boilerplate/pkg/auth"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
"github.com/sujit-baniya/fiber-boilerplate/pkg/services"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)
// Función para renderizar la página de registro
func RegisterGet(c *fiber.Ctx) error {
data := getFlashData(c)
data["title"] = "Register | "
if err := c.Render("auth/register", data, "layouts/landing"); err != nil { //nolint:wsl
panic(err.Error())
}
return nil
}
// Función para manejar la solicitud de registro
func RegisterPost(c *fiber.Ctx) error {
register := c.Locals("register").(models.RegisterForm)
user, err := register.Signup()
if err != nil {
return app.Http.Flash.WithError(c, fiber.Map{
"error": true,
"message": "Error on register request: " + err.Error(),
}).Redirect("/register")
}
store := app.Http.Session.Get(c) // obtener/crear nueva sesión
store.Set("user_id", user.ID) // guardar en el almacenamiento
_ = store.Save()
go services.SendConfirmationEmail(user.Email, app.Http.Server.Url, user.NombreUsuario)
return c.Redirect("/")
}
// Función para verificar el correo electrónico registrado
func VerifyRegisteredEmail(c *fiber.Ctx) error {
return c.Redirect("/app")
}
// Función para reenviar el correo de confirmación
func ResendConfirmEmail(c *fiber.Ctx) error {
user, _ := auth.User(c)
go services.SendConfirmationEmail(user.Email, app.Http.Server.Url, user.NombreUsuario)
return c.Redirect("/")
}
func ReenvioEmail(c *fiber.Ctx) error {
usuarioID := c.Params("id") // Usa Params para obtener el parámetro de la ruta
user, err := models.GetUserById(usuarioID)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"success": false,
"message": "Usuario no encontrado: " + err.Error(),
})
}
// Reenvío del correo electrónico de forma asíncrona
go services.SendConfirmationEmail(user.Email, app.Http.Server.Url, user.NombreUsuario)
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"success": true,
"message": "Email reenviado correctamente",
})
}
// Función para manejar la solicitud de restablecimiento de contraseña
func RequestPasswordResetPost(c *fiber.Ctx) error {
// Crear una estructura para mapear los datos entrantes
type Request struct {
NombreUsuario string `json:"nombre_usuario"` // Mapeo con la clave del JSON
}
var req Request
// Parsear el cuerpo JSON de la solicitud
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"success": false,
"message": "Invalid JSON format",
})
}
// Obtener el usuario del JSON parseado
usuario := req.NombreUsuario
fmt.Println("Usuario recibido:", usuario)
// Intentar obtener el usuario por nombre de usuario
user, err := models.GetUserByUsuario(usuario)
if err != nil || user == nil {
// Devolver respuesta de error en formato JSON
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"success": false,
"message": "Usuario inactivo o no existente",
})
}
// Obtener el email del usuario
usuario_email := user.Email
// Imprimir el email en la consola
log.Println("Sending password reset email to:", usuario_email)
// Enviar el correo de restablecimiento de contraseña de forma asíncrona
go services.SendPasswordResetEmail(usuario_email, app.Http.Server.Url)
// Devolver respuesta de éxito en formato JSON
return c.JSON(fiber.Map{
"success": true,
"message": "We've sent an email to reset your password to your registered email address",
})
}
// todo: Generar contrasenas aleatorias /do/generate-password
func generatePassword(length int) (string, error) {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%&"
var password strings.Builder
for i := 0; i < length; i++ {
randomIndex, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
if err != nil {
return "", err
}
password.WriteByte(charset[randomIndex.Int64()])
}
return password.String(), nil
}
func hashPassword(password string) (string, error) {
hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(hashed), nil
}
func updatePassword(nombreUsuario, plainPassword string) error {
var user models.RegisterForm
result := app.Http.Database.DB.Where("nombre_usuario = ?", nombreUsuario).First(&user)
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return errors.New("codigo no pertenece a ningun usuario")
}
if user.Password != "" {
return errors.New("este usuario ya cuenta con una contraseña")
}
hashedPassword, err := hashPassword(plainPassword)
if err != nil {
return err
}
user.Password = hashedPassword
if err := app.Http.Database.DB.Save(&user).Error; err != nil {
return err
}
return nil
}
func GeneratePasswordPost(c *fiber.Ctx) error {
// Parsear el cuerpo de la solicitud
var body struct {
NombreUsuario string `json:"nombre_usuario"`
}
if err := c.BodyParser(&body); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"success": false,
"message": "Error al analizar el cuerpo de la solicitud",
})
}
if body.NombreUsuario == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"success": false,
"message": "El codigo de usuario es requerido",
})
}
// Generar la contraseña
password, err := generatePassword(12)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"success": false,
"message": "Error generando contraseña",
})
}
// Actualizar la contraseña en la base de datos
if err := updatePassword(body.NombreUsuario, password); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"success": false,
"message": err.Error(),
})
}
// Responder con la contraseña generada
return c.JSON(fiber.Map{
"success": true,
"message": "Contrasena generada exitosamente",
"password": password,
})
}
// Función para renderizar la página de restablecimiento de contraseña
func PasswordReset(c *fiber.Ctx) error {
token := c.Query("t")
if err := c.Render("auth/password-reset", fiber.Map{
"Title": "Password Reset",
"Token": token,
}, "layouts/landing"); err != nil {
panic(err.Error())
}
return nil
}
func PasswordResetPost(c *fiber.Ctx) error {
// Recuperar el nombre de usuario y la nueva contraseña del formulario
nombreUsuario := c.FormValue("nombre_usuario")
password := c.FormValue("password")
// Validar que ambos campos estén presentes
if nombreUsuario == "" || password == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Los campos nombre_usuario y password son obligatorios.",
"error": true,
})
}
// Imprimir los datos recibidos (opcional para depuración)
fmt.Printf("Datos recibidos: nombre_usuario=%s, password=%s\n", nombreUsuario, password)
// Buscar al usuario en la base de datos por nombre de usuario
user, err := models.GetUserByUsuario(nombreUsuario)
if err != nil || user == nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"message": "Usuario no encontrado.",
"error": true,
})
}
hash := config.Hash{}
// Encripta la nueva contraseña
hashedPassword, err := hash.Create(password)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error al encriptar la contraseña",
"error": true,
})
}
// Asigna la contraseña encriptada al modelo
password = hashedPassword
// Actualizar la contraseña del usuario
err = models.UpdateUserPassword(user.ID, password)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error al actualizar la contraseña.",
"error": true,
})
}
// Redirigir al usuario al formulario de inicio de sesión después del éxito
return c.Redirect("/login")
}
// Función para renderizar la página de solicitud de restablecimiento de contraseña
func RequestPasswordReset(c *fiber.Ctx) error {
data := getFlashData(c)
data["title"] = "Password Reset | "
if err := c.Render("auth/request-password-reset", data, "layouts/landing"); err != nil { //nolint:wsl
panic(err.Error())
}
return nil
}
func TokenVencido(c *fiber.Ctx) error {
// Renderiza la vista con el layout layouts/main
if err := c.Render("token_vencido", nil, "layouts/landing"); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error rendering template",
"error": err.Error(),
})
}
return nil
}
+117
View File
@@ -0,0 +1,117 @@
package controllers
import (
"fmt"
"github.com/gofiber/fiber/v2"
"github.com/sujit-baniya/fiber-boilerplate/app"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
"gorm.io/gorm"
)
func CreateNewRole(c *fiber.Ctx) error {
var role models.Role
c.BodyParser(&role)
app.Http.Database.Create(&role)
return c.JSON(role)
}
func RemoveRole(c *fiber.Ctx) error {
var role models.Role
c.BodyParser(&role)
app.Http.Database.
Delete(&models.RoleAndPermission{})
app.Http.Auth.Enforcer.LoadPolicy()
return nil
}
func AssignRoleToUser(c *fiber.Ctx) error {
var roleRequest models.RoleRequest
var role1 models.RoleAndPermission
c.BodyParser(&roleRequest)
role := models.RoleAndPermission{
Ptype: "g",
V0: fmt.Sprintf("%d", roleRequest.UserID),
V1: roleRequest.Role,
}
err := app.Http.Database.Unscoped().First(&role1, models.RoleAndPermission{Ptype: role.Ptype,V0: role.V0, V1: role.V1}).Error
if err != nil {
app.Http.Database.Create(&role)
app.Http.Auth.Enforcer.LoadPolicy()
return c.JSON(role)
}
role1.DeletedAt = gorm.DeletedAt{Valid: false}
app.Http.Database.Unscoped().Save(role1)
app.Http.Auth.Enforcer.LoadPolicy()
return c.JSON(role1)
}
func RevokeRoleFromUser(c *fiber.Ctx) error {
var roleRequest models.RoleRequest
c.BodyParser(&roleRequest)
app.Http.Database.
Delete(
&models.RoleAndPermission{},
models.RoleAndPermission{V0: fmt.Sprintf("%d", roleRequest.UserID), V1: roleRequest.Role})
app.Http.Auth.Enforcer.LoadPolicy()
return c.JSON("Role Revoked from user")
}
func ChangeRoleForUser(c *fiber.Ctx) error {
var role models.RoleRequest
var role1 models.RoleAndPermission
c.BodyParser(&role)
err := app.Http.Database.Unscoped().First(&role1, models.RoleAndPermission{Ptype: "g",V0: fmt.Sprintf("%d", role.UserID), V1: role.OldRole}).Error
if err == nil {
role1.V1 = role.Role
role1.DeletedAt = gorm.DeletedAt{Valid: false}
app.Http.Database.Unscoped().Save(role1)
app.Http.Auth.Enforcer.LoadPolicy()
return c.JSON("Role Changed for user")
}
return c.JSON("Role doesn't exists")
}
func AddPermissionOnRole(c *fiber.Ctx) error {
var permission models.PermissionRequest
var role1 models.RoleAndPermission
c.BodyParser(&permission)
if permission.Role != "" && permission.Module != "" && permission.Action != "" {
role := models.RoleAndPermission{
Ptype: "p",
V0: permission.Role,
V1: permission.Module,
V2: permission.Action,
Category: "permission",
}
err := app.Http.Database.Unscoped().First(&role1, models.RoleAndPermission{Ptype: role.Ptype,V0: role.V0, V1: role.V1, V2: role.V2}).Error
if err != nil {
app.Http.Database.Create(&role)
}
role1.DeletedAt = gorm.DeletedAt{Valid: false}
app.Http.Database.Unscoped().Save(role1)
}
if permission.Role != "" && permission.Route != "" && permission.Method != "" {
fmt.Println(1)
role := models.RoleAndPermission{
Ptype: "p",
V0: permission.Role,
V1: permission.Route,
V2: permission.Method,
Category: "route",
}
err := app.Http.Database.Unscoped().First(&role1, models.RoleAndPermission{Ptype: role.Ptype,V0: role.V0, V1: role.V1, V2: role.V2}).Error
if err != nil {
app.Http.Database.Create(&role)
}
role1.DeletedAt = gorm.DeletedAt{Valid: false}
app.Http.Database.Unscoped().Save(role1)
}
app.Http.Auth.Enforcer.LoadPolicy()
return nil
}
func RemovePermissionFromRole(c *fiber.Ctx) error {
return nil
}
func ChangePermissionOnRole(c *fiber.Ctx) error {
return nil
}
+196
View File
@@ -0,0 +1,196 @@
package controllers
import (
"math"
"strconv"
"github.com/gofiber/fiber/v2" //nolint:goimports
"github.com/sujit-baniya/fiber-boilerplate/app"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
"gorm.io/gorm"
)
// RENDER
func Roles(c *fiber.Ctx) error {
// Obtener los módulos organizados desde el contexto
// Renderizar la vista "roles" y pasar los datos al frontend
data := fiber.Map{
"user": c.Locals("user").(map[string]interface{}),
"modules": c.Locals("userModules"),
}
// Renderiza la vista "roles" con el layout "layouts/main"
if err := c.Render("roles", data, "layouts/main"); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error rendering template",
"error": err.Error(),
})
}
return nil
}
// CONSULTAR TODOS LOS registroa
func GetRoles(c *fiber.Ctx) error {
// Obtener parámetros de consulta para la paginación
pageStr := c.Query("page", "1") // Por defecto, la página es 1
page, err := strconv.Atoi(pageStr)
if err != nil || page < 1 {
page = 1
}
searchQuery := c.Query("search", "") // Obtener el término de búsqueda
limit := 10 // Número de módulos por página
offset := (page - 1) * limit
// Llama a AllModules con el término de búsqueda
roles, total, err := models.AllRoles(limit, offset, searchQuery)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error retrieving modules",
"error": err.Error(),
})
}
modules, err := models.AllModulesSelect()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error retrieving modules",
"error": err.Error(),
})
}
// Llama a AllRoles sin el término de búsqueda para obtener todos los roles
// submodules, err := models.AllSubmodulesSelect() // Pasamos 0 para limit y offset para obtener todos los roles
// if err != nil {
// return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
// "message": "Error retrieving roles",
// "error": err.Error(),
// })
// }
// Calcular el total de páginas
totalPages := int(math.Ceil(float64(total) / float64(limit)))
// Envía los módulos como respuesta JSON junto con la información de paginación
return c.JSON(fiber.Map{
"roles": roles,
"modules": modules,
"total": total, // Total de disponibles
"totalPages": totalPages, // Total de páginas
"page": page, // Página actual
"limit": limit, // Límites por página
})
}
func UpdateRole(c *fiber.Ctx) error {
var m models.Roles
uid, err := strconv.ParseUint(c.Params("id"), 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "ID inválido",
"error": true,
})
}
// Analiza el cuerpo de la solicitud
if err := c.BodyParser(&m); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Error al analizar el cuerpo de la solicitud",
"error": true,
})
}
// Buscar el rol actual en la base de datos
var existingRole models.Roles
if err := app.Http.Database.DB.Preload("Submodules").First(&existingRole, uid).Error; err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"message": "Rol no encontrado",
"error": true,
})
}
// Eliminar todas las relaciones existentes
if err := app.Http.Database.DB.Model(&existingRole).Association("Submodules").Clear(); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error al eliminar relaciones de submódulos",
"error": true,
})
}
// Establecer el ID del rol
m.ID = uint(uid)
// Agregar los nuevos submódulos
var newSubmodules []models.Submodules
for _, submodule := range m.Submodules {
var newSubmodule models.Submodules
if err := app.Http.Database.DB.First(&newSubmodule, submodule.ID).Error; err == nil {
newSubmodules = append(newSubmodules, newSubmodule)
}
}
// Asignar los nuevos submódulos al rol
m.Submodules = newSubmodules
// Actualizar el rol en la base de datos
if err := app.Http.Database.DB.Session(&gorm.Session{FullSaveAssociations: true}).Save(&m).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": err.Error(),
"error": true,
})
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"message": "Rol actualizado con éxito",
"error": false,
"role": m,
})
}
func CreateRole(c *fiber.Ctx) error {
var m models.Roles
// Parse the request body
if err := c.BodyParser(&m); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Error parsing request body",
"error": true,
})
}
// Create the role in the database
if err := models.CreateRole(m); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": err.Error(),
"error": true,
})
}
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
"message": "Role creado correctamente",
"error": false,
"role": m,
})
}
// ELIMINAR
func DeleteRole(c *fiber.Ctx) error {
uid, err := strconv.ParseUint(c.Params("id"), 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Invalid ID",
"error": true,
})
}
if err := models.DeleteRole(uint(uid)); err != nil { // Ensure you have a function for deleting a module
return c.JSON(fiber.Map{
"message": err.Error(),
"error": true,
})
}
return c.JSON(fiber.Map{
"message": "Modulo eliminado correctamente",
"error": false,
})
}
+747
View File
@@ -0,0 +1,747 @@
package controllers
import (
"fmt"
"math"
"os"
"strconv"
"strings"
"github.com/gofiber/fiber/v2" //nolint:goimports
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
)
// RENDER
func Submodules(c *fiber.Ctx) error {
data := fiber.Map{
"user": c.Locals("user").(map[string]interface{}),
"modules": c.Locals("userModules"),
}
// Renderiza la vista "modules" con el layout "layouts/landing"
if err := c.Render("submodules", data, "layouts/main"); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error rendering template",
"error": err.Error(),
})
}
return nil
}
//CONSULTAR TODOS LOS SUBMODULOS
func GetSubmodules(c *fiber.Ctx) error {
// Obtener parámetros de consulta para la paginación
pageStr := c.Query("page", "1") // Por defecto, la página es 1
page, err := strconv.Atoi(pageStr)
if err != nil || page < 1 {
page = 1
}
searchQuery := c.Query("search", "") // Obtener el término de búsqueda
limit := 10 // Número de módulos por página
offset := (page - 1) * limit
// Llama a AllModules con el término de búsqueda
submodules, total, err := models.AllSubmodules(limit, offset, searchQuery)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error retrieving modules",
"error": err.Error(),
})
}
// Llama a AllRoles sin el término de búsqueda para obtener todos los roles
modules, err := models.AllModulesSelect() // Pasamos 0 para limit y offset para obtener todos los roles
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error retrieving roles",
"error": err.Error(),
})
}
// Calcular el total de páginas
totalPages := int(math.Ceil(float64(total) / float64(limit)))
// Envía los módulos como respuesta JSON junto con la información de paginación
return c.JSON(fiber.Map{
"submodules": submodules,
"modules": modules,
"total": total, // Total de módulos disponibles
"totalPages": totalPages, // Total de páginas
"page": page, // Página actual
"limit": limit, // Límites por página
})
}
// ACTUALIZAR
func UpdateSubmodule(c *fiber.Ctx) error {
var m models.Submodules
uid, err := strconv.ParseUint(c.Params("id"), 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "ID inválido",
"error": true,
})
}
// Analiza el cuerpo de la solicitud
if err := c.BodyParser(&m); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Error al analizar el cuerpo de la solicitud",
"error": true,
})
}
m.ID = uint(uid)
// Actualiza el módulo en la base de datos
if err := models.UpdateSubmodule(m); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": err.Error(),
"error": true,
})
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"message": "Registro actualizado con éxito",
"error": false,
"submodule": m,
})
}
// CREAR SUBMODULO
func CreateSubmodule(c *fiber.Ctx) error {
var m models.Submodules
// Analiza el cuerpo de la solicitud
if err := c.BodyParser(&m); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Error al analizar el cuerpo de la solicitud",
"error": true,
})
}
// Crea el nuevo módulo en la base de datos
if err := models.CreateSubmodule(m); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": err.Error(),
"error": true,
})
}
// Crear el archivo del modelo basado en el nombre del submódulo (title)
title := m.Title
fileName := fmt.Sprintf("C:/laragon/www/gases/fiber-boilerplate/pkg/models/%s.go", strings.ToLower(title))
controllerFileName := fmt.Sprintf("C:/laragon/www/gases/fiber-boilerplate/rest/controllers/%s_controller.go", strings.ToLower(title))
routeFileName := "C:/laragon/www/gases/fiber-boilerplate/rest/routes/user.go"
viewFileName := fmt.Sprintf("C:/laragon/www/gases/fiber-boilerplate/resources/views/%s.html", strings.ToLower(title)) // Ruta del archivo de vista
// Generar el contenido del archivo
content := generateModelFileContent(title)
// Crear y escribir el archivo de modelo
if err := os.WriteFile(fileName, []byte(content), 0644); err != nil {
fmt.Printf("Error al crear el archivo de modelo: %s\n", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error al crear el archivo de modelo",
"error": true,
})
}
fmt.Printf("Archivo de modelo creado: %s\n", fileName)
// Generar el contenido del archivo de controlador
controllerContent := generateControllerFileContent(title)
// Crear y escribir el archivo de controlador
if err := os.WriteFile(controllerFileName, []byte(controllerContent), 0644); err != nil {
fmt.Printf("Error al crear el archivo de controlador: %s\n", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error al crear el archivo de controlador",
"error": true,
})
}
fmt.Printf("Archivo de controlador creado: %s\n", controllerFileName)
// Generar el contenido del archivo de vista
viewContent := generateViewFileContent(title)
// Crear y escribir el archivo de vista
if err := os.WriteFile(viewFileName, []byte(viewContent), 0644); err != nil {
fmt.Printf("Error al crear el archivo de vista: %s\n", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error al crear el archivo de vista",
"error": true,
})
}
fmt.Printf("Archivo de vista creado: %s\n", viewFileName)
// Añadir las rutas al archivo user.go
routeContent := generateRouteFileContent(title)
// Abrir el archivo user.go en modo append y añadir las nuevas rutas
if err := appendToFile(routeFileName, routeContent); err != nil {
fmt.Printf("Error al añadir las rutas al archivo user.go: %s\n", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error al añadir las rutas al archivo user.go",
"error": true,
})
}
fmt.Printf("Rutas añadidas al archivo user.go: %s\n", routeFileName)
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
"message": "Registro creado con éxito",
"error": false,
"submodule": m,
})
}
// Función auxiliar para pluralizar el nombre si es necesario
func pluralize(word string) string {
// Simple heuristic: if the word ends in "s", don't pluralize
if strings.HasSuffix(word, "s") {
return word
}
return word + "s"
}
// Función que genera el contenido del archivo del modelo
func generateModelFileContent(title string) string {
pluralTitle := pluralize(title)
return fmt.Sprintf(`package models
import (
"time"
"gorm.io/gorm"
"github.com/sujit-baniya/fiber-boilerplate/app"
)
type %s struct {
gorm.Model // Embedding gorm.Model to inherit fields like ID, CreatedAt, UpdatedAt, DeletedAt
ID uint gorm:"primarykey"
ModifiedAt time.Time json:"modified_at" gorm:"column:modified_at"
Description string json:"description" gorm:"column:description"
Title string json:"title" gorm:"column:title"
}
// TableName overrides the table name used by %s to %s
func (%s) TableName() string {
return "%s"
}
// All%s recupera todos los registros con búsqueda
func All%s(limit, offset int, search string) ([]%s, int64, error) {
var items []%s
var total int64
db := app.Http.Database.DB.Model(&%s{})
// Filtrar por término de búsqueda si se proporciona
if search != "" {
db = db.Where("title LIKE ? OR description LIKE ?", "%%"+search+"%%", "%%"+search+"%%")
}
// Obtener el total de registros
if err := db.Count(&total).Error; err != nil {
return nil, 0, err
}
// Obtener los registros con paginación
if err := db.Order("created_at DESC").Limit(limit).Offset(offset).Find(&items).Error; err != nil {
return nil, 0, err
}
return items, total, nil
}
// All%sSelect recupera todos los registros sin paginación
func All%sSelect() ([]%s, error) {
var items []%s
// Realizar la consulta para obtener todos los registros
if err := app.Http.Database.DB.Model(&%s{}).Find(&items).Error; err != nil {
return nil, err
}
return items, nil
}
// Create%s creates a new record
func Create%s(item %s) error {
if err := app.Http.Database.DB.Create(&item).Error; err != nil {
return err
}
return nil
}
// Update%s updates an existing record
func Update%s(item %s) error {
if err := app.Http.Database.DB.Save(&item).Error; err != nil {
return err
}
return nil
}
// Delete%s deletes a record by its ID
func Delete%s (id uint) error {
if err := app.Http.Database.DB.Delete(&%s{}, id).Error; err != nil {
return err
}
return nil
}
`,
title, // Nombre de la estructura (singular)
title, title, // TableName usa el nombre en singular para la estructura y la función
title, pluralTitle, // Nombre de la tabla (en plural)
pluralTitle, pluralTitle, title, // Función All para paginación, plural y singular
title, title, // Modelo usado en la consulta
pluralTitle, pluralTitle, title, // AllSelect sin paginación
title, title, title, // Crear, actualizar, borrar registros
title, title, title, title) // Funciones de creación, actualización y eliminación
}
// Función para generar el contenido del archivo de controlador
func generateControllerFileContent(title string) string {
lowerTitle := strings.ToLower(title)
// Contenido del controlador reemplazando "Title" con el nombre del submódulo
return fmt.Sprintf(`package controllers
import (
"github.com/gofiber/fiber/v2"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
"strconv"
"math"
)
// RENDER
func %s(c *fiber.Ctx) error {
// Renderiza la vista con el layout layouts/main
if err := c.Render("%s", nil, "layouts/main"); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error rendering template",
"error": err.Error(),
})
}
return nil
}
// CONSULTAR TODOS LOS REGISTROS
func Get%s(c *fiber.Ctx) error {
// Obtener parámetros de consulta para la paginación
pageStr := c.Query("page", "1") // Por defecto, la página es 1
page, err := strconv.Atoi(pageStr)
if err != nil || page < 1 {
page = 1
}
searchQuery := c.Query("search", "") // Obtener el término de búsqueda
limit := 10 // Número de registros por página
offset := (page - 1) * limit
// Llama a AllModules con el término de búsqueda
records, total, err := models.All%s(limit, offset, searchQuery)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error retrieving records",
"error": err.Error(),
})
}
// Calcular el total de páginas
totalPages := int(math.Ceil(float64(total) / float64(limit)))
// Envía los registros como respuesta JSON junto con la información de paginación
return c.JSON(fiber.Map{
"registros": records,
"total": total, // Total de registros disponibles
"totalPages": totalPages, // Total de páginas
"page": page, // Página actual
"limit": limit, // Límites por página
})
}
// CREAR
func Create%s(c *fiber.Ctx) error {
var m models.%s
// Analiza el cuerpo de la solicitud
if err := c.BodyParser(&m); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Error al analizar el cuerpo de la solicitud",
"error": true,
})
}
// Crea el nuevo registro en la base de datos
if err := models.Create%s(m); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": err.Error(),
"error": true,
})
}
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
"message": "Registro creado con éxito",
"error": false,
"registro": m,
})
}
// ACTUALIZAR
func Update%s(c *fiber.Ctx) error {
var m models.%s
uid, err := strconv.ParseUint(c.Params("id"), 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "ID inválido",
"error": true,
})
}
// Analiza el cuerpo de la solicitud
if err := c.BodyParser(&m); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Error al analizar el cuerpo de la solicitud",
"error": true,
})
}
m.ID = uint(uid)
// Actualiza el módulo en la base de datos
if err := models.Update%s(m); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": err.Error(),
"error": true,
})
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"message": "Registro actualizado con éxito",
"error": false,
"registro": m,
})
}
// Delete%s elimina un %s por su ID
func Delete%s(c *fiber.Ctx) error {
uid, err := strconv.ParseUint(c.Params("id"), 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "ID inválido",
"error": true,
})
}
// Lógica para eliminar %s
if err := models.Delete%s(uint(uid)); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": err.Error(),
"error": true,
})
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"message": "%s eliminado con éxito",
"error": false,
})
}
`, title, lowerTitle, title, title, title, title, title, title, title, lowerTitle, title, title, lowerTitle, title)
}
// Función para generar el contenido de rutas para el nuevo submódulo
func generateRouteFileContent(title string) string {
lowerTitle := strings.ToLower(title)
// Bloque de rutas personalizado
return fmt.Sprintf(`
// Rutas para %s
account.Get("/%s", controllers.Get%s) // Renderizar la vista de %s
account.Get("/load%s", controllers.Get%s) // Obtener todos los %s
account.Post("/%s", controllers.Create%s) // Crear un nuevo %s
account.Put("/%s/:id", controllers.Update%s) // Actualizar un %s existente
account.Delete("/%s/:id", controllers.Delete%s) // Eliminar un %s
`, lowerTitle, lowerTitle, title, lowerTitle, lowerTitle, title, lowerTitle, lowerTitle, title, lowerTitle, lowerTitle, title, lowerTitle, lowerTitle, title)
}
// Función para añadir contenido a un archivo existente
func appendToFile(fileName, content string) error {
f, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer f.Close()
if _, err := f.WriteString(content); err != nil {
return err
}
return nil
}
// generateViewFileContent genera el contenido del archivo de vista para un submódulo
func generateViewFileContent(title string) string {
return fmt.Sprintf(`
<div x-data="app" class="bg-white rounded-lg shadow">
<div x-show="loading" class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-50 justify-center items-center">
<img src="../img/loading.gif" alt="Cargando..." class="w-16 h-16" />
</div>
<div class="container mx-auto p-6 w-full">
<div class="justify-between items-center w-full md:flex">
<div>
<h1 class="text-2xl font-bold mb-2">Lista de actualizaciones de datos de usuario</h1>
<p class="mb-4 text-sm">Gestión de actualizaciones de datos de usuario.</p>
</div>
<div class="mb-4 md:mb-0 relative">
<input type="text" placeholder="Buscar..." class="border border-gray-300 rounded p-2 w-full"
x-model="search" @input.debounce.500ms="loadData()" />
</div>
<div class="flex itmems-center justify-end mb-4 md:mb-0">
<button @click="exportToExcel()" class="bg-[#8eb02f] text-white px-4 py-2 rounded text-sm">Exportar a
Excel</button>
</div>
</div>
<div class="overflow-x-auto">
<table class="table-auto w-full">
<thead class="text-sm text-left py-4 border-b border-gray-300">
<tr class="text-left font-semibold border-collapse">
<th class="py-2 px-4 border-b">Código</th>
<th class="py-2 px-4 border-b">Nombre</th>
<th class="py-2 px-4 border-b"></th>
</tr>
</thead>
<tbody class="text-gray-500 select-none text-sm">
<template x-for="data in datos" :key="data.id">
<tr class="hover:bg-gray-100">
<td class="py-3 text-xs px-4 border-b" x-text="data.codigo"></td>
<td class="py-3 text-xs px-4 border-b" x-text="data.name"></td>
<td class="py-3 text-xs px-4 border-b">
<div class="flex items-center">
<div :class="data.revisado ? 'bg-green-500' : 'bg-gray-400'"
class="w-3 h-3 rounded-full mr-2"></div>
<span x-text="data.revisado ? 'Revisado' : 'No Revisado'"></span>
</div>
</td>
<td class="py-3 text-xs px-4 border-b"
x-text="data.created_at ? new Date(data.created_at).toLocaleDateString('es-ES', { year: 'numeric', month: 'long', day: 'numeric' }) : 'Fecha no disponible'">
</td>
<td class="py-3 text-xs px-4 border-b">
<div class="flex items-center gap-2">
<button @click="openViewModal(data)" title="Ver">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-5 text-[#8eb02f]">
<path stroke-linecap="round" stroke-linejoin="round"
d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
<path stroke-linecap="round" stroke-linejoin="round"
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</button>
</div>
</td>
</tr>
</template>
</tbody>
</table>
</div>
<div class="flex justify-start items-center gap-1 p-4">
<button @click="page = Math.max(1, page - 1); loadData()" :disabled="page === 1"
class="px-4 py-2 bg-gray-300 rounded disabled:opacity-50">
Anterior
</button>
<template x-for="pageNum in paginatedPages" :key="pageNum">
<button @click="goToPage(pageNum)" class="px-4 py-2 bg-gray-300 rounded "
:class="{ 'bg-blue-500 text-white': pageNum === page }" x-text="pageNum"></button>
</template>
<button @click="page += 1; loadData()" :disabled="datos.length < limit" :disabled="page === totalPages"
class="px-4 py-2 bg-gray-300 rounded disabled:opacity-50">
Siguiente
</button>
</div>
</div>
<!-- Modal Ver -->
<div x-show="viewModal"
class="fixed inset-0 overflow-auto bg-gray-800 bg-opacity-75 flex justify-center items-center z-50">
<div class="bg-white rounded-lg shadow-lg p-6 w-11/12 max-w-lg">
<h2 class="text-lg font-bold mb-4">Ver actualizacion de datos de usuario</h2>
<div class="w-full mb-4">
<label class="block mb-2">Tipo de usuario:</label>
<input type="text" x-model="selectedItem.tipo_usuario" disabled
class="border border-gray-300 rounded p-2 w-full" placeholder="Ingresa el tipo de usuario" />
</div>
<div class="flex flex-col sm:flex-row justify-between gap-4 mb-4">
<div class="w-full">
<label class="block mb-2">Tipo de persona:</label>
<input type="text" x-model="selectedItem.tipo_persona" disabled
class="border border-gray-300 rounded p-2 w-full" placeholder="Ingresa el tipo de persona" />
</div>
<div class="w-full">
<label class="block mb-2">Nombre:</label>
<input type="text" x-model="selectedItem.name" disabled
class="border border-gray-300 rounded p-2 w-full" placeholder="Ingresa el nombre" />
</div>
</div>
<div class="flex justify-end gap-2">
<button @click="viewModal = false" class="px-4 py-2 bg-gray-300 rounded">Cerrar</button>
</div>
</div>
</div>
</div>
<script>
function app() {
return {
datos: [],
loading: false,
limit: 10,
page: 1,
search: '',
totalPages: 0,
paginatedPages: [],
// modales
viewModal: false,
item: {
},
selectedItem: {
id: '',
tipo_persona: '',
name: '',
},
loadData() {
if (this.search !== '') {
this.page = 1;
this.limit = 100;
} else {
this.limit = 10;
}
this.loading = true;
axios.get('/app/loadactualizaciondatosusuarios', {
params: {
page: this.page,
limit: this.limit,
search: this.search
}
}).then(response => {
this.datos = response.data.registros.map(data => ({
id: data.ID,
tipo_persona: data.tipo_persona,
name: data.name,
created_at: data.CreatedAt,
}));
this.totalPages = response.data.totalPages;
this.calculatePaginatedPages();
}).catch(error => {
console.error('Error al obtener datos:', error);
}).finally(() => {
this.loading = false;
});
},
calculatePaginatedPages() {
const maxVisiblePages = 5;
const pages = [];
const startPage = Math.max(1, this.page - Math.floor(maxVisiblePages / 2));
const endPage = Math.min(this.totalPages, startPage + maxVisiblePages - 1);
for (let i = startPage; i <= endPage; i++) {
pages.push(i);
}
this.paginatedPages = pages;
},
goToPage(page) {
this.page = page;
this.loadData();
},
init() {
this.loadData();
},
openViewModal(data) {
this.selectedItem.id = data.id;
this.selectedItem.tipo_persona = data.tipo_persona;
this.selectedItem.name = data.name;
this.viewModal = true;
},
exportToExcel() {
axios.get('/app/export/actualizacion-datos-usuarios', {
responseType: 'blob', // Importante para manejar el archivo binario
}).then(response => {
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'actualizacion_datos_usuarios.xlsx'); // Nombre del archivo
document.body.appendChild(link);
link.click();
}).catch(error => {
console.error('Error al exportar datos:', error);
});
}
}
}
</script>
`)
}
// ELIMINAR
func DeleteSubmodule(c *fiber.Ctx) error {
uid, err := strconv.ParseUint(c.Params("id"), 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Invalid ID",
"error": true,
})
}
if err := models.DeleteSumodule(uint(uid)); err != nil { // Ensure you have a function for deleting a module
return c.JSON(fiber.Map{
"message": err.Error(),
"error": true,
})
}
return c.JSON(fiber.Map{
"message": "Registro eliminado correctamente",
"error": false,
})
}
+49
View File
@@ -0,0 +1,49 @@
package controllers
import (
"github.com/gofiber/fiber/v2" //nolint:goimports
"github.com/sujit-baniya/fiber-boilerplate/pkg/auth"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
"strconv"
)
func UserInfo(c *fiber.Ctx) error {
users, err := models.GetUserById(c.Params("id"))
if err != nil {
return c.JSON(fiber.Map{
"message": err.Error(),
"error": true,
})
}
return c.JSON(users)
}
func Me(c *fiber.Ctx) error {
user, _ := auth.User(c)
return c.JSON(user)
}
func UserSettings(c *fiber.Ctx) error {
uid, _ := strconv.ParseUint(c.Params("id"), 10, 32)
user := models.User{
ID: uint(uid),
}
settings, err := user.Settings()
if err != nil {
return c.JSON(fiber.Map{
"message": err.Error(),
"error": true,
})
}
return c.JSON(settings)
}
func StoreUserSettings(c *fiber.Ctx) error {
var userSettings models.UserSetting
c.BodyParser(&userSettings)
uid, _ := strconv.ParseUint(c.Params("id"), 10, 32)
userSettings.UserID = uint(uid)
userSettings.UpdateOrCreate()
return c.JSON(userSettings)
}
+556
View File
@@ -0,0 +1,556 @@
package controllers
import (
"fmt"
"math"
"strconv"
"github.com/gofiber/fiber/v2" //nolint:goimports
"github.com/sujit-baniya/fiber-boilerplate/app"
"github.com/sujit-baniya/fiber-boilerplate/config"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
"github.com/sujit-baniya/fiber-boilerplate/pkg/services"
)
// RENDER
func Users(c *fiber.Ctx) error {
data := fiber.Map{
"user": c.Locals("user").(map[string]interface{}),
"modules": c.Locals("userModules"),
}
// Renderiza la vista "modules" con el layout "layouts/landing"
if err := c.Render("users", data, "layouts/main"); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error rendering template",
"error": err.Error(),
})
}
return nil
}
// RENDER usuarios gas
func UsersGas(c *fiber.Ctx) error {
data := fiber.Map{
"user": c.Locals("user").(map[string]interface{}),
"modules": c.Locals("userModules"),
}
// Renderiza la vista "modules" con el layout "layouts/landing"
if err := c.Render("users_gas", data, "layouts/main"); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error rendering template",
"error": err.Error(),
})
}
return nil
}
// CONSULTAR TODOS LOS REGISTROS
func GetUsers(c *fiber.Ctx) error {
// Obtener parámetros de consulta para la paginación
pageStr := c.Query("page", "1") // Por defecto, la página es 1
page, err := strconv.Atoi(pageStr)
if err != nil || page < 1 {
page = 1
}
searchQuery := c.Query("search", "") // Obtener el término de búsqueda
limit := 10 // Número de módulos por página
offset := (page - 1) * limit
// Llama a AllUsers con el término de búsqueda
Users, totalUsers, err := models.AllUsersSistema(limit, offset, searchQuery)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error retrieving users",
"error": err.Error(),
})
}
// Llama a AllRoles sin el término de búsqueda para obtener todos los roles
roles, err := models.AllRolesSelect() // Pasamos 0 para limit y offset para obtener todos los roles
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error retrieving roles",
"error": err.Error(),
})
}
// Calcular el total de páginas para usuarios
totalPages := int(math.Ceil(float64(totalUsers) / float64(limit)))
// Envía los usuarios y roles como respuesta JSON junto con la información de paginación
return c.JSON(fiber.Map{
"Users": Users,
"Roles": roles,
"total": totalUsers, // Total de usuarios disponibles
"totalPages": totalPages, // Total de páginas
"page": page, // Página actual
"limit": limit, // Límites por página
})
}
// CONSULTAR TODOS LOS REGISTROS GAS
func GetusersGas(c *fiber.Ctx) error {
// Obtener parámetros de consulta para la paginación
pageStr := c.Query("page", "1") // Por defecto, la página es 1
page, err := strconv.Atoi(pageStr)
if err != nil || page < 1 {
page = 1
}
searchQuery := c.Query("search", "") // Obtener el término de búsqueda
limit := 10 // Número de módulos por página
offset := (page - 1) * limit
// Llama a AllUsers con el término de búsqueda
Users, totalUsers, err := models.AllUsersGas(limit, offset, searchQuery)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error retrieving users",
"error": err.Error(),
})
}
// Calcular el total de páginas para usuarios
totalPages := int(math.Ceil(float64(totalUsers) / float64(limit)))
// Envía los usuarios y roles como respuesta JSON junto con la información de paginación
return c.JSON(fiber.Map{
"Users": Users,
"total": totalUsers, // Total de usuarios disponibles
"totalPages": totalPages, // Total de páginas
"page": page, // Página actual
"limit": limit, // Límites por página
})
}
// GetUser consulta un solo registro de usuario por su ID
func GetUser(c *fiber.Ctx) error {
// Obtiene el ID del parámetro de la URL
idParam := c.Params("id")
fmt.Print(idParam)
// Convierte el parámetro a un uint
userID, err := strconv.ParseUint(idParam, 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Invalid user ID",
"error": err.Error(),
})
}
// Canal para recibir el resultado de la consulta
resultChan := make(chan *models.Users)
errorChan := make(chan error)
// Ejecuta la consulta en una goroutine
go func() {
user, err := models.FindUserByID(uint(userID))
if err != nil {
errorChan <- err
return
}
// Si no hay error, envía el resultado al canal
resultChan <- user
}()
// Espera el resultado o el error de la goroutine
select {
case user := <-resultChan:
// Si el usuario no existe
if user == nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"message": "User not found",
})
}
// Envía el usuario como respuesta JSON
return c.JSON(fiber.Map{
"user": user,
})
case err := <-errorChan:
// Maneja el error
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error retrieving user",
"error": err.Error(),
})
}
}
func UpdateUser(c *fiber.Ctx) error {
var m models.Users
uid, err := strconv.ParseUint(c.Params("id"), 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "ID inválido",
"error": true,
})
}
// Analiza el cuerpo de la solicitud
if err := c.BodyParser(&m); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Error al analizar el cuerpo de la solicitud",
"error": true,
})
}
m.ID = uint(uid)
// Si el campo Estado está presente, solo se actualiza ese campo
if m.Estado != nil {
// Actualiza el estado del usuario
if err := models.UpdateUserEstado(m.ID, *m.Estado); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": err.Error(),
"error": true,
})
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"message": "Estado del usuario actualizado con éxito",
"error": false,
"user": m,
})
}
// Verifica que al menos dos de los campos relevantes estén llenos (Name, NombreUsuario, Email)
if (m.Name == "" && m.NombreUsuario == "" && m.Email == "") ||
(m.Name == "" && m.Email == "") ||
(m.NombreUsuario == "" && m.Email == "") {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Se deben llenar al menos dos de los siguientes campos: Name, NombreUsuario, Email",
"error": true,
})
}
// Crea un mapa para los campos que se actualizarán
updates := make(map[string]interface{})
// Agrega los campos que están presentes en la solicitud
if m.Name != "" {
updates["Name"] = m.Name
}
if m.NombreUsuario != "" {
updates["NombreUsuario"] = m.NombreUsuario
}
if m.Email != "" {
updates["email"] = m.Email
}
if m.RoleID != 0 {
updates["role_id"] = m.RoleID
}
// Realiza la actualización de los campos seleccionados
if err := models.UpdateUser(m.ID, m.Name, m.NombreUsuario, m.Email, int(m.RoleID)); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": err.Error(),
"error": true,
})
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"message": "Usuario actualizado con éxito",
"error": false,
"user": m,
})
}
func UpdateUserGas(c *fiber.Ctx) error {
var m models.Users
uid, err := strconv.ParseUint(c.Params("id"), 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "ID inválido",
"error": true,
})
}
var existing models.Users
if err := app.Http.Database.DB.First(&existing, uid).Error; err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"message": "Registro no encontrado",
"error": true,
})
}
if err := c.BodyParser(&m); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Error al analizar el cuerpo de la solicitud",
"error": true,
})
}
if m.Name != "" {
existing.Name = m.Name
}
if m.NombreUsuario != "" {
existing.NombreUsuario = m.NombreUsuario
}
if m.NumeroMedidor != "" {
existing.NumeroMedidor = m.NumeroMedidor
}
if m.Email != "" {
existing.Email = m.Email
}
if m.Celular != "" {
existing.Celular = m.Celular
}
if err := app.Http.Database.DB.Save(&existing).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error al guardar los cambios",
"error": true,
})
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"message": "Usuario actualizado con éxito",
"error": false,
"user": existing,
})
}
// ACTUALIZAR
func UpdatePassword(c *fiber.Ctx) error {
var m models.Users
uid, err := strconv.ParseUint(c.Params("id"), 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "ID inválido",
"error": true,
})
}
// Analiza el cuerpo de la solicitud
if err := c.BodyParser(&m); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Error al analizar el cuerpo de la solicitud",
"error": true,
})
}
m.ID = uint(uid)
// Verifica si hay un nuevo password para actualizar
if m.Password != "" {
// Crea una instancia de Hash
hash := config.Hash{}
// Encripta la nueva contraseña
hashedPassword, err := hash.Create(m.Password)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error al encriptar la contraseña",
"error": true,
})
}
// Asigna la contraseña encriptada al modelo
m.Password = hashedPassword
// Actualiza solo el campo de contraseña en la base de datos
if err := models.UpdateUserPassword(m.ID, m.Password); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": err.Error(),
"error": true,
})
}
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"message": "Contraseña actualizada con éxito",
"error": false,
})
}
// CREAR
func CreateUser(c *fiber.Ctx) error {
var m models.Users
// Analiza el cuerpo de la solicitud
if err := c.BodyParser(&m); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Error al analizar el cuerpo de la solicitud",
"error": true,
})
}
// Crea el nuevo módulo en la base de datos
if err := models.CreateUser(m); err != nil { // Asegúrate de tener una función para crear un módulo
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": err.Error(),
"error": true,
})
}
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
"message": "Usuario creado con éxito",
"error": false,
"user": m,
})
}
// ELIMINAR
func DeleteUser(c *fiber.Ctx) error {
uid, err := strconv.ParseUint(c.Params("id"), 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Invalid ID",
"error": true,
})
}
if err := models.DeleteUser(uint(uid)); err != nil { // Ensure you have a function for deleting a module
return c.JSON(fiber.Map{
"message": err.Error(),
"error": true,
})
}
return c.JSON(fiber.Map{
"message": "Usuario eliminado correctamente",
"error": false,
})
}
func CreateUserGas(c *fiber.Ctx) error {
var m models.Users
// Analiza el cuerpo de la solicitud
if err := c.BodyParser(&m); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Error al analizar el cuerpo de la solicitud",
"error": true,
})
}
// Verifica si el nombre de usuario ya existe
exists, err := models.CheckUsernameExists(m.NombreUsuario)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error al verificar el nombre de usuario",
"error": err.Error(),
})
}
if exists {
return c.Status(fiber.StatusConflict).JSON(fiber.Map{
"message": "El codigo ya se encuentra registrado",
"error": true,
})
}
// Debug: imprimir los datos que se pasan a GetValidarMedidor
fmt.Println("Llamando a GetValidarMedidor con NombreUsuario:", m.NombreUsuario, "y NumeroMedidor:", m.NumeroMedidor)
// Aquí llamamos a la función GetValidarMedidor pasándole el código y medidor directamente desde el cuerpo de la solicitud
// Obtén el RoleID basado en el RoleName
roleName := "Usuarios"
role, err := models.FindRoleByName(roleName)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error al obtener el rol",
"error": err.Error(),
})
}
// Asigna el RoleID al usuario
m.RoleID = role.ID
m.TipoUsuario = "gas"
estado := true
m.Estado = &estado
m.Password = ""
// Crea el nuevo usuario en la base de datos (sin asignar el ID manualmente)
if err := models.CreateUser(m); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": err.Error(),
"error": true,
})
}
user, err := models.GetUserByUsuarioVerify(m.NombreUsuario)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error retrieving user",
"error": err.Error(),
})
}
// Aquí el modelo `m` debe tener el ID generado por la base de datos
fmt.Println("Nuevo usuario creado con ID:", user.ID)
// Llama a la función createPassword pasando el ID del nuevo usuario
if err := createPassword(c, user.ID); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error al actualizar la contraseña",
"error": true,
})
}
// Envía un correo de confirmación
go services.SendConfirmationEmail(m.Email, app.Http.Server.Url, m.NombreUsuario)
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
"message": "Usuario creado con éxito",
"error": false,
"user": m,
})
}
func createPassword(c *fiber.Ctx, UserId uint) error {
// Modelo para recibir los datos del cuerpo de la solicitud
type PasswordUpdateRequest struct {
Password string `json:"password"`
}
var req PasswordUpdateRequest
// Analiza el cuerpo de la solicitud
fmt.Println("Recibiendo solicitud para actualizar contraseña...")
if err := c.BodyParser(&req); err != nil {
fmt.Println("Error al analizar el cuerpo de la solicitud:", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Error al analizar el cuerpo de la solicitud",
"error": true,
})
}
// Verifica si el password no está vacío
if req.Password == "" {
fmt.Println("La contraseña no puede estar vacía")
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "La contraseña no puede estar vacía",
"error": true,
})
}
// Crea una instancia del encriptador de contraseñas
hash := config.Hash{}
// Encripta la nueva contraseña
fmt.Println("Encriptando la contraseña...")
hashedPassword, err := hash.Create(req.Password)
if err != nil {
fmt.Println("Error al encriptar la contraseña:", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error al encriptar la contraseña",
"error": true,
})
}
fmt.Println("Contraseña encriptada con éxito.")
// Actualiza solo el campo de contraseña en la base de datos
fmt.Printf("Actualizando la contraseña del usuario con ID %d\n", UserId)
if err := models.UpdateUserPassword(UserId, hashedPassword); err != nil {
fmt.Println("Error al actualizar la contraseña en la base de datos:", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error al actualizar la contraseña en la base de datos",
"error": true,
})
}
// Respuesta de éxito
fmt.Println("Contraseña actualizada con éxito.")
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"message": "Contraseña actualizada con éxito",
"error": false,
})
}