diff --git a/API_ADMIN_GUIA.md b/API_ADMIN_GUIA.md new file mode 100644 index 0000000..458b5b6 --- /dev/null +++ b/API_ADMIN_GUIA.md @@ -0,0 +1,315 @@ +# API Admin — Guia de Implementacion + +> Stack: Laravel + MongoDB + Laravel Sanctum +> Prefijo base: /api/admin/* +> Seguridad: Bearer Token (Sanctum) + rol administrador + +--- + +## 1. Archivos creados / modificados + +| Archivo | Descripcion | +|---------|-------------| +| app/Http/Middleware/AdminApiMiddleware.php | Verifica rol=administrador. Retorna JSON 403 (sin redirect). | +| app/Http/Kernel.php | Registra alias admin.api | +| app/Http/Controllers/Api/Admin/UsuarioController.php | CRUD usuarios + activar/desactivar | +| app/Http/Controllers/Api/Admin/VcardController.php | Listar, ver, editar vcards; por usuario | +| app/Http/Controllers/Api/Admin/MembresiaController.php | Ver/activar/desactivar membresia + cambiar plan | +| app/Http/Controllers/Api/Admin/PagoController.php | Historial HealthPagos + transacciones wallet | +| app/Http/Controllers/Api/Admin/LogController.php | Log de movimientos global y por usuario | +| app/Http/Controllers/Api/Admin/MiniWebController.php | Miniwebs con usuario y vcard vinculada | +| routes/api.php | 34 rutas nuevas bajo /api/admin/* | + +--- + +## 2. Implementar en otro proyecto + +### 2.1 Requisitos previos +- Laravel >= 10 +- mongodb/laravel-mongodb instalado +- laravel/sanctum configurado +- Modelo User con HasApiTokens y relacion rol() -> Rol +- Modelo Rol con campo nombre (valor "administrador") + +### 2.2 Copiar estos archivos +``` +app/Http/Middleware/AdminApiMiddleware.php +app/Http/Controllers/Api/Admin/UsuarioController.php +app/Http/Controllers/Api/Admin/VcardController.php +app/Http/Controllers/Api/Admin/MembresiaController.php +app/Http/Controllers/Api/Admin/PagoController.php +app/Http/Controllers/Api/Admin/LogController.php +app/Http/Controllers/Api/Admin/MiniWebController.php +``` + +### 2.3 Registrar middleware en app/Http/Kernel.php +```php +protected $routeMiddleware = [ + // ... existentes ... + 'admin.api' => \App\Http\Middleware\AdminApiMiddleware::class, +]; +``` + +### 2.4 Agregar rutas en routes/api.php +```php +use App\Http\Controllers\Api\Admin\UsuarioController as AdminUsuario; +use App\Http\Controllers\Api\Admin\VcardController as AdminVcard; +use App\Http\Controllers\Api\Admin\MembresiaController as AdminMembresia; +use App\Http\Controllers\Api\Admin\PagoController as AdminPago; +use App\Http\Controllers\Api\Admin\LogController as AdminLog; +use App\Http\Controllers\Api\Admin\MiniWebController as AdminMiniWeb; + +Route::middleware(['auth:sanctum', 'admin.api'])->prefix('admin')->name('admin.')->group(function () { + + // Usuarios + Route::get('usuarios', [AdminUsuario::class, 'index']); + Route::get('usuarios/{id}', [AdminUsuario::class, 'show']); + Route::put('usuarios/{id}', [AdminUsuario::class, 'update']); + Route::post('usuarios/{id}/activar', [AdminUsuario::class, 'activar']); + Route::post('usuarios/{id}/desactivar', [AdminUsuario::class, 'desactivar']); + + // VCards + Route::get('vcards', [AdminVcard::class, 'index']); + Route::get('vcards/{id}', [AdminVcard::class, 'show']); + Route::put('vcards/{id}', [AdminVcard::class, 'update']); + Route::get('usuarios/{userId}/vcards', [AdminVcard::class, 'byUsuario']); + + // Membresias + Route::get('planes', [AdminMembresia::class, 'planes']); + Route::get('usuarios/{id}/membresia', [AdminMembresia::class, 'show']); + Route::post('usuarios/{id}/activar-membresia', [AdminMembresia::class, 'activar']); + Route::post('usuarios/{id}/desactivar-membresia', [AdminMembresia::class, 'desactivar']); + Route::post('usuarios/{id}/cambiar-plan', [AdminMembresia::class, 'cambiarPlan']); + + // Pagos + Route::get('pagos', [AdminPago::class, 'index']); + Route::get('transacciones', [AdminPago::class, 'transacciones']); + Route::get('usuarios/{userId}/pagos', [AdminPago::class, 'byUsuario']); + Route::get('usuarios/{userId}/transacciones', [AdminPago::class, 'transaccionesByUsuario']); + + // Logs + Route::get('logs', [AdminLog::class, 'index']); + Route::get('usuarios/{userId}/logs', [AdminLog::class, 'byUsuario']); + + // MiniWebs + Route::get('miniwebs', [AdminMiniWeb::class, 'index']); + Route::get('miniwebs/{id}', [AdminMiniWeb::class, 'show']); + Route::get('usuarios/{userId}/miniwebs', [AdminMiniWeb::class, 'byUsuario']); +}); +``` + +--- + +## 3. Flujo de autenticacion + +### Login +```http +POST /api/login +Content-Type: application/json + +{ + "email": "admin@example.com", + "password": "secret" +} +``` +Respuesta: +```json +{ + "access_token": "1|abc123...", + "token_type": "Bearer", + "user": { ... } +} +``` + +### Usar token en cada peticion +``` +Authorization: Bearer 1|abc123... +``` + +### Logout +```http +POST /api/logout +Authorization: Bearer 1|abc123... +``` + +--- + +## 4. Referencia de endpoints + +### Usuarios /api/admin/usuarios + +| Metodo | Ruta | Descripcion | Query params | +|--------|------|-------------|--------------| +| GET | /api/admin/usuarios | Lista paginada | search, estado, plan_id, per_page | +| GET | /api/admin/usuarios/{id} | Detalle: plan, vcards, wallet, perfil | — | +| PUT | /api/admin/usuarios/{id} | Edita name, email, rol_id, plan_id | — | +| POST | /api/admin/usuarios/{id}/activar | Activa cuenta (estado=true) | — | +| POST | /api/admin/usuarios/{id}/desactivar | Desactiva cuenta (estado=false) | — | + +Ejemplo PUT: +```json +{ "name": "Juan Garcia", "email": "juan@empresa.com", "plan_id": "664abc123" } +``` + +--- + +### VCards /api/admin/vcards + +| Metodo | Ruta | Descripcion | Query params | +|--------|------|-------------|--------------| +| GET | /api/admin/vcards | Lista paginada | search, estado, user_id, per_page | +| GET | /api/admin/vcards/{id} | Detalle con usuario | — | +| PUT | /api/admin/vcards/{id} | Edita datos basicos | — | +| GET | /api/admin/usuarios/{userId}/vcards | Vcards de un usuario | — | + +Campos editables: nombre, cargo, descripcion, telefono, whatsapp, email, empresa, +direccion, website, pais, ciudad, estado, privacidad, es_borrador + +--- + +### Membresias + +| Metodo | Ruta | Descripcion | Body | +|--------|------|-------------|------| +| GET | /api/admin/planes | Lista planes activos | — | +| GET | /api/admin/usuarios/{id}/membresia | Estado de membresia | — | +| POST | /api/admin/usuarios/{id}/activar-membresia | Activa/renueva | { "anual": true, "dias": 365 } | +| POST | /api/admin/usuarios/{id}/cambiar-plan | Cambia plan | { "plan_id": "abc", "anual": false } | +| POST | /api/admin/usuarios/{id}/desactivar-membresia | Desactiva | — | + +Logica activar-membresia: +- Si ya tiene membresia vigente, los dias se SUMAN (no se pisa la fecha). +- anual:true = +365 dias. anual:false = +30 dias (o los dias que se indiquen). + +--- + +### Pagos + +| Metodo | Ruta | Descripcion | Query params | +|--------|------|-------------|--------------| +| GET | /api/admin/pagos | Historial global HealthPagos | user_id, estado, moneda, desde, hasta, per_page | +| GET | /api/admin/transacciones | Transacciones wallet global | type, status, per_page | +| GET | /api/admin/usuarios/{id}/pagos | Pagos de un usuario + total pagado | — | +| GET | /api/admin/usuarios/{id}/transacciones | Wallet de un usuario | — | + +Valores estado (HealthPago): pendiente, completado, fallido +Valores type (Transaction): deposit, withdraw, transfer_in, transfer_out, purchase, adjustment +Valores status (Transaction): pending, completed, failed + +--- + +### Logs + +| Metodo | Ruta | Descripcion | Query params | +|--------|------|-------------|--------------| +| GET | /api/admin/logs | Log global de movimientos | user_id, type, search, desde, hasta, per_page | +| GET | /api/admin/usuarios/{id}/logs | Logs de un usuario | — | + +Valores type: create, edit, delete, delete cuenta + +--- + +### MiniWebs + +| Metodo | Ruta | Descripcion | Query params | +|--------|------|-------------|--------------| +| GET | /api/admin/miniwebs | Lista paginada | search, user_id, per_page | +| GET | /api/admin/miniwebs/{id} | Detalle con usuario + vcard vinculada | — | +| GET | /api/admin/usuarios/{id}/miniwebs | Miniwebs de un usuario | — | + +--- + +## 5. Respuestas de error + +| HTTP | Cuando | +|------|--------| +| 401 | Token ausente o invalido | +| 403 | Autenticado pero sin rol administrador | +| 404 | Recurso no encontrado | +| 422 | Validacion fallida | + +Ejemplo 403: +```json +{ + "error": "Acceso denegado. Se requieren privilegios de administrador.", + "code": 403 +} +``` + +--- + +## 6. Modelos requeridos + +| Modelo | Coleccion MongoDB | Relaciones usadas | +|--------|------------------|-------------------| +| User | users | rol, plan, perfil, wallet, vcards, setup | +| Vcard | vcard | user | +| Planes | planes | tarifas | +| HealthPago | health_pagos | — | +| Transaction | transactions | wallet | +| Wallet | wallets | user | +| Log | log | user | +| MiniWeb | miniwebs | user, vcard | +| Rol | rol | — | + +--- + +## 7. Paginacion + +Todos los listados retornan estructura estandar de Laravel: +```json +{ + "current_page": 1, + "data": [ ... ], + "per_page": 20, + "total": 150, + "last_page": 8, + "next_page_url": "https://...", + "prev_page_url": null +} +``` +Maximo per_page: 100 + +--- + +## 8. Prueba rapida con curl + +```bash +# 1. Login y guardar token +TOKEN=$(curl -s -X POST https://tudominio.com/api/login \ + -H "Content-Type: application/json" \ + -d '{"email":"admin@example.com","password":"secret"}' \ + | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])") + +# 2. Listar usuarios +curl https://tudominio.com/api/admin/usuarios \ + -H "Authorization: Bearer $TOKEN" + +# 3. Vcards de un usuario +curl https://tudominio.com/api/admin/usuarios/USER_ID/vcards \ + -H "Authorization: Bearer $TOKEN" + +# 4. Activar membresia anual +curl -X POST https://tudominio.com/api/admin/usuarios/USER_ID/activar-membresia \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"anual": true}' + +# 5. Cambiar plan +curl -X POST https://tudominio.com/api/admin/usuarios/USER_ID/cambiar-plan \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"plan_id": "PLAN_ID"}' + +# 6. Historial de pagos con filtros +curl "https://tudominio.com/api/admin/pagos?estado=completado&desde=2026-01-01&per_page=50" \ + -H "Authorization: Bearer $TOKEN" + +# 7. Logs de un usuario +curl https://tudominio.com/api/admin/usuarios/USER_ID/logs \ + -H "Authorization: Bearer $TOKEN" + +# 8. MiniWebs con busqueda +curl "https://tudominio.com/api/admin/miniwebs?search=empresa" \ + -H "Authorization: Bearer $TOKEN" +``` diff --git a/build_output.txt b/build_output.txt new file mode 100644 index 0000000..e69de29 diff --git a/migrations/migrate.go b/migrations/migrate.go index 00d85c9..890f29b 100755 --- a/migrations/migrate.go +++ b/migrations/migrate.go @@ -90,6 +90,8 @@ func Migrate() { // Submódulo Partner &models.PartnerRecurso{}, &models.PartnerComunicado{}, + // Integración VCard API Admin (Laravel) + &models.VcardApiConfig{}, ); err != nil { log.Fatalf("Error during main migration: %v", err) } 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/resources/views/vcard_api.html b/resources/views/vcard_api.html new file mode 100644 index 0000000..1172dab --- /dev/null +++ b/resources/views/vcard_api.html @@ -0,0 +1,639 @@ + +
+ + +
+ Cargando... +
+ +
+ + +
+
+

VCard API

+

Integración con el sistema VCard externo (Laravel + Sanctum). Gestiona usuarios, vcards, membresías y más.

+
+ +
+ + +
+ Sin configuración activa. Haz clic en "Configurar API" para ingresar la URL base y el Bearer Token del sistema VCard. +
+ + +
+ + + + Inactiva +
+ + +
+ +
+ + +
+ + +
+ + +
+ + +
+
Sin datos. Haz clic en "Cargar".
+
+ + + + + + + + + + + + + +
NombreEmailPlanEstadoAcciones
+
+
+ + +
+
Sin datos. Haz clic en "Cargar".
+
+ + + + + + + + + + + + + +
NombreEmpresaEmailEstadoPrivacidad
+
+
+ + +
+
Sin datos. Haz clic en "Cargar".
+
+ +
+
+ + +
+
Sin datos. Haz clic en "Cargar".
+
+ + + + + + + + + + + + + +
ReferenciaUsuarioMontoEstadoFecha
+
+
+ + +
+
Sin datos. Haz clic en "Cargar".
+
+ + + + + + + + + + + + + +
TipoMontoEstadoDescripciónFecha
+
+
+ + +
+
Sin datos. Haz clic en "Cargar".
+
+ + + + + + + + + + + + +
TipoUsuarioDescripciónFecha
+
+
+ + +
+
Sin datos. Haz clic en "Cargar".
+
+ + + + + + + + + + + + +
TítuloUsuarioVCard vinculadaEstado
+
+
+ + +
+ Total: +
+ + + +
+
+ +
+ + + + + + + +
+ + diff --git a/rest/controllers/vcard_api_controller.go b/rest/controllers/vcard_api_controller.go new file mode 100644 index 0000000..59d5dc7 --- /dev/null +++ b/rest/controllers/vcard_api_controller.go @@ -0,0 +1,248 @@ +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) { + return vcardDoWithBody(method, endpoint, nil, "", cfg) +} + +// vcardDoWithBody ejecuta una llamada a la API Admin VCard con body opcional. +func vcardDoWithBody(method, endpoint string, reqBody io.Reader, contentType 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, reqBody) + if err != nil { + return nil, 0, err + } + req.Header.Set("Authorization", "Bearer "+cfg.BearerToken) + req.Header.Set("Accept", "application/json") + if contentType != "" { + req.Header.Set("Content-Type", contentType) + } + + 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"}) + } + + 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) +} + +// proxyVcardMutate reenvía el body del request original a la API VCard. +func proxyVcardMutate(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"}) + } + + var bodyReader io.Reader + ct := "" + if raw := c.Body(); len(raw) > 0 { + bodyReader = strings.NewReader(string(raw)) + ct = c.Get("Content-Type", "application/json") + } + + body, status, err := vcardDoWithBody(method, endpoint, bodyReader, ct, 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") +} + +// ─── Mutación endpoints ─────────────────────────────────────────────────────── + +func VcardApiUsuarioUpdate(c *fiber.Ctx) error { + return proxyVcardMutate(c, http.MethodPut, "/api/admin/usuarios/"+c.Params("id")) +} + +func VcardApiUsuarioActivar(c *fiber.Ctx) error { + return proxyVcardMutate(c, http.MethodPost, "/api/admin/usuarios/"+c.Params("id")+"/activar") +} + +func VcardApiUsuarioDesactivar(c *fiber.Ctx) error { + return proxyVcardMutate(c, http.MethodPost, "/api/admin/usuarios/"+c.Params("id")+"/desactivar") +} + +func VcardApiVcardUpdate(c *fiber.Ctx) error { + return proxyVcardMutate(c, http.MethodPut, "/api/admin/vcards/"+c.Params("id")) +} + +func VcardApiActivarMembresia(c *fiber.Ctx) error { + return proxyVcardMutate(c, http.MethodPost, "/api/admin/usuarios/"+c.Params("id")+"/activar-membresia") +} + +func VcardApiDesactivarMembresia(c *fiber.Ctx) error { + return proxyVcardMutate(c, http.MethodPost, "/api/admin/usuarios/"+c.Params("id")+"/desactivar-membresia") +} + +func VcardApiCambiarPlan(c *fiber.Ctx) error { + return proxyVcardMutate(c, http.MethodPost, "/api/admin/usuarios/"+c.Params("id")+"/cambiar-plan") +} diff --git a/rest/routes/user.go b/rest/routes/user.go index 2243334..5aab020 100755 --- a/rest/routes/user.go +++ b/rest/routes/user.go @@ -21,7 +21,7 @@ func UserRoutes(app fiber.Router) { // Rutas de la aplicación - // web me redireccione a / + // web me redireccione a / protected.Get("/web", func(c *fiber.Ctx) error { return c.Redirect("/", http.StatusSeeOther) }) app.Get("/web", func(c *fiber.Ctx) error { return c.Redirect("/", http.StatusSeeOther) }) protected.Get("/", controllers.App) @@ -215,6 +215,38 @@ func UserRoutes(app fiber.Router) { protected.Get("/saas-api/logs", middlewares.MenuMiddleware, controllers.SaasDispatchLogIndex) protected.Get("/loadsaasdispatchlogs", controllers.GetSaasDispatchLogs) + // ─── 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) + // Usuarios (GET) + 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) + // Usuarios (mutaciones) + protected.Put("/vcard-api/usuarios/:id", controllers.VcardApiUsuarioUpdate) + protected.Post("/vcard-api/usuarios/:id/activar", controllers.VcardApiUsuarioActivar) + protected.Post("/vcard-api/usuarios/:id/desactivar", controllers.VcardApiUsuarioDesactivar) + protected.Post("/vcard-api/usuarios/:id/activar-membresia", controllers.VcardApiActivarMembresia) + protected.Post("/vcard-api/usuarios/:id/desactivar-membresia", controllers.VcardApiDesactivarMembresia) + protected.Post("/vcard-api/usuarios/:id/cambiar-plan", controllers.VcardApiCambiarPlan) + // VCards + protected.Get("/vcard-api/vcards", controllers.VcardApiVcards) + protected.Get("/vcard-api/vcards/:id", controllers.VcardApiVcard) + protected.Put("/vcard-api/vcards/:id", controllers.VcardApiVcardUpdate) + // Resto (solo GET) + 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) diff --git a/seed_integraciones.sql b/seed_integraciones.sql index e3ce624..52e41d4 100644 --- a/seed_integraciones.sql +++ b/seed_integraciones.sql @@ -35,12 +35,23 @@ WHERE NOT EXISTS ( SELECT 1 FROM submodules WHERE url = '/app/cloudflare' AND deleted_at IS NULL ); --- 4. Asignar ambos submódulos a TODOS los roles (ignorar duplicados) +-- 4. Insertar submódulo VCard API si no existe +INSERT INTO submodules (title, description, url, module_id, modified_at, created_at, updated_at) +SELECT 'VCard API', + 'Integración con el sistema VCard externo (Laravel + Sanctum): usuarios, membresías, vcards, pagos y más', + '/app/vcard-api', + (SELECT id FROM modules WHERE title = 'Integraciones' AND deleted_at IS NULL LIMIT 1), + NOW(), NOW(), NOW() +WHERE NOT EXISTS ( + SELECT 1 FROM submodules WHERE url = '/app/vcard-api' AND deleted_at IS NULL +); + +-- 5. Asignar todos los submódulos de Integraciones a TODOS los roles (ignorar duplicados) INSERT INTO roles_submodules (role_id, submodule_id) SELECT r.id, s.id FROM roles r CROSS JOIN submodules s -WHERE s.url IN ('/app/hostinger', '/app/cloudflare') +WHERE s.url IN ('/app/hostinger', '/app/cloudflare', '/app/vcard-api') AND r.deleted_at IS NULL AND s.deleted_at IS NULL ON CONFLICT DO NOTHING;