From 5024ae2dd3bbe241deda65c264046faf1c6131f7 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Tue, 19 May 2026 20:18:31 -0500 Subject: [PATCH] up --- pkg/models/vcard_api_config.go | 53 +++++++ rest/controllers/vcard_api_controller.go | 182 +++++++++++++++++++++++ rest/routes/user.go | 23 ++- 3 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 pkg/models/vcard_api_config.go create mode 100644 rest/controllers/vcard_api_controller.go diff --git a/pkg/models/vcard_api_config.go b/pkg/models/vcard_api_config.go new file mode 100644 index 0000000..ff90c49 --- /dev/null +++ b/pkg/models/vcard_api_config.go @@ -0,0 +1,53 @@ +package models + +import ( +"github.com/sujit-baniya/fiber-boilerplate/app" +"gorm.io/gorm" +) + +// VcardApiConfig almacena las credenciales para conectar con la API Admin del +// sistema VCard externo (Laravel + Sanctum, prefijo /api/admin/*). +type VcardApiConfig struct { +gorm.Model +Nombre string `json:"nombre" gorm:"column:nombre;not null"` +BaseURL string `json:"base_url" gorm:"column:base_url;type:text;not null"` +BearerToken string `json:"bearer_token" gorm:"column:bearer_token;type:text;not null"` +Activo bool `json:"activo" gorm:"column:activo;default:true"` +} + +func (VcardApiConfig) TableName() string { return "vcard_api_configs" } + +func GetVcardApiConfig() (*VcardApiConfig, error) { +var cfg VcardApiConfig +if err := app.Http.Database.DB.First(&cfg).Error; err != nil { +return nil, err +} +return &cfg, nil +} + +func UpsertVcardApiConfig(nombre, baseURL, bearerToken string, activo bool) (*VcardApiConfig, error) { +var cfg VcardApiConfig +err := app.Http.Database.DB.First(&cfg).Error +if err != nil { +cfg = VcardApiConfig{ +Nombre: nombre, +BaseURL: baseURL, +BearerToken: bearerToken, +Activo: activo, +} +if createErr := app.Http.Database.DB.Create(&cfg).Error; createErr != nil { +return nil, createErr +} +return &cfg, nil +} +cfg.Nombre = nombre +cfg.BaseURL = baseURL +if bearerToken != "" { +cfg.BearerToken = bearerToken +} +cfg.Activo = activo +if err := app.Http.Database.DB.Save(&cfg).Error; err != nil { +return nil, err +} +return &cfg, nil +} diff --git a/rest/controllers/vcard_api_controller.go b/rest/controllers/vcard_api_controller.go new file mode 100644 index 0000000..f6c7ba0 --- /dev/null +++ b/rest/controllers/vcard_api_controller.go @@ -0,0 +1,182 @@ +package controllers + +import ( +"encoding/json" +"fmt" +"io" +"net/http" +"strings" +"time" + +"github.com/gofiber/fiber/v2" +"github.com/sujit-baniya/fiber-boilerplate/pkg/models" +) + +// VcardApiIndex renderiza el panel de integración VCard. +func VcardApiIndex(c *fiber.Ctx) error { +cfg, _ := models.GetVcardApiConfig() +data := fiber.Map{ +"user": c.Locals("user").(map[string]interface{}), +"modules": c.Locals("userModules"), +"config": cfg, +} +return c.Render("vcard_api", data, "layouts/main") +} + +// VcardApiSaveConfig guarda / actualiza la configuración (base URL + token). +func VcardApiSaveConfig(c *fiber.Ctx) error { +type Req struct { +Nombre string `json:"nombre"` +BaseURL string `json:"base_url"` +BearerToken string `json:"bearer_token"` +Activo bool `json:"activo"` +} +var req Req +if err := c.BodyParser(&req); err != nil { +return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "body inválido"}) +} +if strings.TrimSpace(req.BaseURL) == "" { +return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "base_url es requerido"}) +} +cfg, err := models.UpsertVcardApiConfig(req.Nombre, strings.TrimRight(strings.TrimSpace(req.BaseURL), "/"), req.BearerToken, req.Activo) +if err != nil { +return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) +} +return c.JSON(fiber.Map{"ok": true, "config": cfg}) +} + +// VcardApiGetConfig devuelve la configuración actual (sin exponer el token completo). +func VcardApiGetConfig(c *fiber.Ctx) error { +cfg, err := models.GetVcardApiConfig() +if err != nil { +return c.JSON(fiber.Map{"config": nil}) +} +masked := "••••••••" +if cfg.BearerToken == "" { +masked = "" +} +return c.JSON(fiber.Map{ +"config": fiber.Map{ +"ID": cfg.ID, +"nombre": cfg.Nombre, +"base_url": cfg.BaseURL, +"bearer_token": masked, +"activo": cfg.Activo, +}, +}) +} + +// vcardDo ejecuta una llamada a la API Admin VCard y devuelve el body como json.RawMessage. +func vcardDo(method, endpoint string, cfg *models.VcardApiConfig) ([]byte, int, error) { +url := fmt.Sprintf("%s%s", cfg.BaseURL, endpoint) +client := &http.Client{Timeout: 15 * time.Second} +req, err := http.NewRequest(method, url, nil) +if err != nil { +return nil, 0, err +} +req.Header.Set("Authorization", "Bearer "+cfg.BearerToken) +req.Header.Set("Accept", "application/json") + +resp, err := client.Do(req) +if err != nil { +return nil, 0, err +} +defer resp.Body.Close() +body, _ := io.ReadAll(io.LimitReader(resp.Body, 512*1024)) +return body, resp.StatusCode, nil +} + +// proxyVcard extrae la config, llama al endpoint y devuelve el resultado al frontend. +func proxyVcard(c *fiber.Ctx, method, endpoint string) error { +cfg, err := models.GetVcardApiConfig() +if err != nil { +return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Configura primero la API VCard"}) +} +if !cfg.Activo { +return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "La integración está inactiva"}) +} + +// Pasar query params del request original +qs := string(c.Request().URI().QueryString()) +ep := endpoint +if qs != "" { +ep = endpoint + "?" + qs +} + +body, status, err := vcardDo(method, ep, cfg) +if err != nil { +return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"error": err.Error()}) +} +var result json.RawMessage +if err := json.Unmarshal(body, &result); err != nil { +result = json.RawMessage(fmt.Sprintf(`{"raw": %q}`, string(body))) +} +c.Status(status) +return c.JSON(result) +} + +// ─── Proxy endpoints ────────────────────────────────────────────────────────── + +func VcardApiUsuarios(c *fiber.Ctx) error { +return proxyVcard(c, http.MethodGet, "/api/admin/usuarios") +} + +func VcardApiUsuario(c *fiber.Ctx) error { +return proxyVcard(c, http.MethodGet, "/api/admin/usuarios/"+c.Params("id")) +} + +func VcardApiVcards(c *fiber.Ctx) error { +return proxyVcard(c, http.MethodGet, "/api/admin/vcards") +} + +func VcardApiVcard(c *fiber.Ctx) error { +return proxyVcard(c, http.MethodGet, "/api/admin/vcards/"+c.Params("id")) +} + +func VcardApiVcardsByUsuario(c *fiber.Ctx) error { +return proxyVcard(c, http.MethodGet, "/api/admin/usuarios/"+c.Params("userId")+"/vcards") +} + +func VcardApiPlanes(c *fiber.Ctx) error { +return proxyVcard(c, http.MethodGet, "/api/admin/planes") +} + +func VcardApiMembresia(c *fiber.Ctx) error { +return proxyVcard(c, http.MethodGet, "/api/admin/usuarios/"+c.Params("id")+"/membresia") +} + +func VcardApiPagos(c *fiber.Ctx) error { +return proxyVcard(c, http.MethodGet, "/api/admin/pagos") +} + +func VcardApiPagosByUsuario(c *fiber.Ctx) error { +return proxyVcard(c, http.MethodGet, "/api/admin/usuarios/"+c.Params("userId")+"/pagos") +} + +func VcardApiTransacciones(c *fiber.Ctx) error { +return proxyVcard(c, http.MethodGet, "/api/admin/transacciones") +} + +func VcardApiTransaccionesByUsuario(c *fiber.Ctx) error { +return proxyVcard(c, http.MethodGet, "/api/admin/usuarios/"+c.Params("userId")+"/transacciones") +} + +func VcardApiLogs(c *fiber.Ctx) error { +return proxyVcard(c, http.MethodGet, "/api/admin/logs") +} + +func VcardApiLogsByUsuario(c *fiber.Ctx) error { +return proxyVcard(c, http.MethodGet, "/api/admin/usuarios/"+c.Params("userId")+"/logs") +} + +func VcardApiMiniwebs(c *fiber.Ctx) error { +return proxyVcard(c, http.MethodGet, "/api/admin/miniwebs") +} + +func VcardApiMiniweb(c *fiber.Ctx) error { +return proxyVcard(c, http.MethodGet, "/api/admin/miniwebs/"+c.Params("id")) +} + +func VcardApiMiniwebsByUsuario(c *fiber.Ctx) error { +return proxyVcard(c, http.MethodGet, "/api/admin/usuarios/"+c.Params("userId")+"/miniwebs") +} diff --git a/rest/routes/user.go b/rest/routes/user.go index 2243334..77f8732 100755 --- a/rest/routes/user.go +++ b/rest/routes/user.go @@ -215,7 +215,28 @@ func UserRoutes(app fiber.Router) { protected.Get("/saas-api/logs", middlewares.MenuMiddleware, controllers.SaasDispatchLogIndex) protected.Get("/loadsaasdispatchlogs", controllers.GetSaasDispatchLogs) - // ─── Telegram ───────────────────────────────────────────────────────────── + // ─── VCard API (integración Admin Laravel) ──────────────────────────────── +protected.Get("/vcard-api", middlewares.MenuMiddleware, controllers.VcardApiIndex) +protected.Get("/vcard-api/config", controllers.VcardApiGetConfig) +protected.Post("/vcard-api/config", controllers.VcardApiSaveConfig) +protected.Get("/vcard-api/usuarios", controllers.VcardApiUsuarios) +protected.Get("/vcard-api/usuarios/:id/membresia", controllers.VcardApiMembresia) +protected.Get("/vcard-api/usuarios/:userId/vcards", controllers.VcardApiVcardsByUsuario) +protected.Get("/vcard-api/usuarios/:userId/pagos", controllers.VcardApiPagosByUsuario) +protected.Get("/vcard-api/usuarios/:userId/transacciones", controllers.VcardApiTransaccionesByUsuario) +protected.Get("/vcard-api/usuarios/:userId/logs", controllers.VcardApiLogsByUsuario) +protected.Get("/vcard-api/usuarios/:userId/miniwebs", controllers.VcardApiMiniwebsByUsuario) +protected.Get("/vcard-api/usuarios/:id", controllers.VcardApiUsuario) +protected.Get("/vcard-api/vcards", controllers.VcardApiVcards) +protected.Get("/vcard-api/vcards/:id", controllers.VcardApiVcard) +protected.Get("/vcard-api/planes", controllers.VcardApiPlanes) +protected.Get("/vcard-api/pagos", controllers.VcardApiPagos) +protected.Get("/vcard-api/transacciones", controllers.VcardApiTransacciones) +protected.Get("/vcard-api/logs", controllers.VcardApiLogs) +protected.Get("/vcard-api/miniwebs", controllers.VcardApiMiniwebs) +protected.Get("/vcard-api/miniwebs/:id", controllers.VcardApiMiniweb) + +// ─── Telegram ───────────────────────────────────────────────────────────── protected.Get("/telegram", middlewares.MenuMiddleware, controllers.TelegramIndex) protected.Get("/loadtelegram", controllers.GetTelegramConfigs) protected.Post("/telegram", controllers.CreateTelegramConfig)