Prepare Beta Version
This commit is contained in:
parent
a7002701f5
commit
ea6b04f3f4
117
config/auth.php
Normal file
117
config/auth.php
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Authentication Defaults
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option defines the default authentication "guard" and password
|
||||||
|
| reset "broker" for your application. You may change these values
|
||||||
|
| as required, but they're a perfect start for most applications.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'defaults' => [
|
||||||
|
'guard' => env('AUTH_GUARD', 'web'),
|
||||||
|
'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Authentication Guards
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Next, you may define every authentication guard for your application.
|
||||||
|
| Of course, a great default configuration has been defined for you
|
||||||
|
| which utilizes session storage plus the Eloquent user provider.
|
||||||
|
|
|
||||||
|
| All authentication guards have a user provider, which defines how the
|
||||||
|
| users are actually retrieved out of your database or other storage
|
||||||
|
| system used by the application. Typically, Eloquent is utilized.
|
||||||
|
|
|
||||||
|
| Supported: "session"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'guards' => [
|
||||||
|
'web' => [
|
||||||
|
'driver' => 'session',
|
||||||
|
'provider' => 'users',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| User Providers
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| All authentication guards have a user provider, which defines how the
|
||||||
|
| users are actually retrieved out of your database or other storage
|
||||||
|
| system used by the application. Typically, Eloquent is utilized.
|
||||||
|
|
|
||||||
|
| If you have multiple user tables or models you may configure multiple
|
||||||
|
| providers to represent the model / table. These providers may then
|
||||||
|
| be assigned to any extra authentication guards you have defined.
|
||||||
|
|
|
||||||
|
| Supported: "database", "eloquent"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'providers' => [
|
||||||
|
'users' => [
|
||||||
|
'driver' => 'eloquent',
|
||||||
|
'model' => env('AUTH_MODEL', Koneko\VuexyAdmin\Models\User::class),
|
||||||
|
],
|
||||||
|
|
||||||
|
// 'users' => [
|
||||||
|
// 'driver' => 'database',
|
||||||
|
// 'table' => 'users',
|
||||||
|
// ],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Resetting Passwords
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| These configuration options specify the behavior of Laravel's password
|
||||||
|
| reset functionality, including the table utilized for token storage
|
||||||
|
| and the user provider that is invoked to actually retrieve users.
|
||||||
|
|
|
||||||
|
| The expiry time is the number of minutes that each reset token will be
|
||||||
|
| considered valid. This security feature keeps tokens short-lived so
|
||||||
|
| they have less time to be guessed. You may change this as needed.
|
||||||
|
|
|
||||||
|
| The throttle setting is the number of seconds a user must wait before
|
||||||
|
| generating more password reset tokens. This prevents the user from
|
||||||
|
| quickly generating a very large amount of password reset tokens.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'passwords' => [
|
||||||
|
'users' => [
|
||||||
|
'provider' => 'users',
|
||||||
|
'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
|
||||||
|
'expire' => 60,
|
||||||
|
'throttle' => 60,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Password Confirmation Timeout
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may define the amount of seconds before a password confirmation
|
||||||
|
| window expires and users are asked to re-enter their password via the
|
||||||
|
| confirmation screen. By default, the timeout lasts for three hours.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
|
||||||
|
|
||||||
|
];
|
@ -1,20 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'connections' => [
|
|
||||||
'keyvault' => [
|
|
||||||
'driver' => 'mysql',
|
|
||||||
'host' => env('KEYVAULT_DB_HOST', '127.0.0.1'),
|
|
||||||
'database' => env('KEYVAULT_DB_DATABASE', 'key_vault'),
|
|
||||||
'username' => env('KEYVAULT_DB_USERNAME', 'vault_user'),
|
|
||||||
'password' => env('KEYVAULT_DB_PASSWORD', 'secret'),
|
|
||||||
'charset' => 'utf8mb4',
|
|
||||||
'collation' => 'utf8mb4_unicode_ci',
|
|
||||||
'prefix' => env('KEYVAULT_DB_PREFIX', ''),
|
|
||||||
'strict' => true,
|
|
||||||
'engine' => null,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
// Variables
|
|
||||||
return [
|
return [
|
||||||
"title" => "Koneko Soluciones Tecnológicas",
|
"title" => "Koneko Soluciones Tecnológicas",
|
||||||
"description" => "Koneko Soluciones Tecnológicas ofrece desarrollo de sistemas empresariales, sitios web profesionales, inteligencia artificial, infraestructura y soluciones digitales avanzadas para negocios en México.",
|
"description" => "Koneko Soluciones Tecnológicas ofrece desarrollo de sistemas empresariales, sitios web profesionales, inteligencia artificial, infraestructura y soluciones digitales avanzadas para negocios en México.",
|
||||||
@ -7,4 +7,18 @@ return [
|
|||||||
"app_name" => "koneko.mx",
|
"app_name" => "koneko.mx",
|
||||||
"app_logo" => "../vendor/vuexy-admin/img/logo/koneko-04.png",
|
"app_logo" => "../vendor/vuexy-admin/img/logo/koneko-04.png",
|
||||||
"favicon" => "../vendor/vuexy-admin/img/logo/koneko-04.png",
|
"favicon" => "../vendor/vuexy-admin/img/logo/koneko-04.png",
|
||||||
|
|
||||||
|
// ================== 📦 CACHE GENERAL ==================
|
||||||
|
'cache' => [
|
||||||
|
'enabled' => (bool) env('KONEKO_CACHE_ENABLED', true),
|
||||||
|
'ttl' => (int) env('KONEKO_CACHE_TTL', 20 * 24 * 60), // 20 días
|
||||||
|
],
|
||||||
|
|
||||||
|
// ================== 📦 CACHE DE COMPONENTE ==================
|
||||||
|
'core' => [
|
||||||
|
'cache' => [
|
||||||
|
'enabled' => (bool) env('KONEKO_CORE_CACHE_ENABLED', true),
|
||||||
|
'ttl' => (int) env('KONEKO_CORE_CACHE_TTL', 20 * 24 * 60),
|
||||||
|
],
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
@ -1,107 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
return [
|
|
||||||
// Personalización de interfaz
|
|
||||||
'vuexy' => [
|
|
||||||
'myLayout' => 'horizontal', // Options[String]: vertical(default), horizontal
|
|
||||||
'myTheme' => 'theme-semi-dark', // Options[String]: theme-default(default), theme-bordered, theme-semi-dark
|
|
||||||
'myStyle' => 'light', // Options[String]: light(default), dark & system mode
|
|
||||||
'myRTLSupport' => false, // options[Boolean]: true(default), false // To provide RTLSupport or not
|
|
||||||
'myRTLMode' => false, // options[Boolean]: false(default), true // To set layout to RTL layout (myRTLSupport must be true for rtl mode)
|
|
||||||
'hasCustomizer' => true, // options[Boolean]: true(default), false // Display customizer or not THIS WILL REMOVE INCLUDED JS FILE. SO LOCAL STORAGE WON'T WORK
|
|
||||||
'displayCustomizer' => true, // options[Boolean]: true(default), false // Display customizer UI or not, THIS WON'T REMOVE INCLUDED JS FILE. SO LOCAL STORAGE WILL WORK
|
|
||||||
'contentLayout' => 'compact', // options[String]: 'compact', 'wide' (compact=container-xxl, wide=container-fluid)
|
|
||||||
'navbarType' => 'static', // options[String]: 'sticky', 'static', 'hidden' (Only for vertical Layout)
|
|
||||||
'footerFixed' => false, // options[Boolean]: false(default), true // Footer Fixed
|
|
||||||
'menuFixed' => false, // options[Boolean]: true(default), false // Layout(menu) Fixed (Only for vertical Layout)
|
|
||||||
'menuCollapsed' => true, // options[Boolean]: false(default), true // Show menu collapsed, (Only for vertical Layout)
|
|
||||||
'headerType' => 'static', // options[String]: 'static', 'fixed' (for horizontal layout only)
|
|
||||||
'showDropdownOnHover' => false, // true, false (for horizontal layout only)
|
|
||||||
'authViewMode' => 'cover', // Options[String]: cover(default), basic
|
|
||||||
'maxQuickLinks' => 8, // options[Integer]: 8(default), 6, 8, 10
|
|
||||||
'customizerControls' => [
|
|
||||||
'style',
|
|
||||||
'headerType',
|
|
||||||
'contentLayout',
|
|
||||||
'layoutCollapsed',
|
|
||||||
'layoutNavbarOptions',
|
|
||||||
'themes',
|
|
||||||
], // To show/hide customizer options
|
|
||||||
],
|
|
||||||
|
|
||||||
// HTTPS y proxies
|
|
||||||
'security' => [
|
|
||||||
'force_https' => (bool) env('FORCE_HTTPS', false),
|
|
||||||
'trust_proxy' => (bool) env('TRUST_PROXY', false),
|
|
||||||
'trust_proxy_ips' => env('TRUST_PROXY_IPS', '*'),
|
|
||||||
|
|
||||||
// Key Vault & Encryption Management
|
|
||||||
'key_vault' => [
|
|
||||||
'driver' => env('VUEXY_KEY_VAULT_DRIVER', 'laravel'), // Options: laravel, sqlite, mysql, mariadb, go_service
|
|
||||||
'enabled' => (bool) env('VUEXY_KEY_VAULT_ENABLED', true),
|
|
||||||
|
|
||||||
// Laravel Default Encryption (APP_KEY)
|
|
||||||
'laravel' => [
|
|
||||||
'key' => env('APP_KEY'),
|
|
||||||
'algorithm' => 'AES-256-CBC',
|
|
||||||
],
|
|
||||||
|
|
||||||
// Second DB Configuration (Requires separate connection)
|
|
||||||
'database' => [
|
|
||||||
'connection' => env('VUEXY_KEY_VAULT_DB_CONNECTION', 'vault'),
|
|
||||||
'table' => env('VUEXY_KEY_VAULT_DB_TABLE', 'vault_keys'),
|
|
||||||
'algorithm' => env('VUEXY_KEY_VAULT_DB_ALGORITHM', 'AES-256-CBC'),
|
|
||||||
],
|
|
||||||
|
|
||||||
// External Go Microservice
|
|
||||||
'service' => [
|
|
||||||
'base_url' => env('VUEXY_KEY_VAULT_SERVICE_URL'),
|
|
||||||
'api_token' => env('VUEXY_KEY_VAULT_SERVICE_TOKEN'),
|
|
||||||
'timeout' => (int) env('VUEXY_KEY_VAULT_SERVICE_TIMEOUT', 5),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
// Cache
|
|
||||||
'cache' => [
|
|
||||||
'enabled' => (bool) env('VUEXY_CACHE_ENABLED', true),
|
|
||||||
'ttl' => (int) env('VUEXY_CACHE_TTL', 20 * 24 * 60),
|
|
||||||
],
|
|
||||||
|
|
||||||
// Avatar
|
|
||||||
'avatar' => [
|
|
||||||
'initials' => [
|
|
||||||
'max_length' => (int) env('VUEXY_AVATAR_INITIALS_MAX_LENGTH', 2),
|
|
||||||
'disk' => env('VUEXY_AVATAR_INITIALS_DISK', 'public'),
|
|
||||||
'directory' => env('VUEXY_AVATAR_INITIALS_DIRECTORY', 'initial-avatars'),
|
|
||||||
'size' => (int) env('VUEXY_AVATAR_INITIALS_SIZE', 512),
|
|
||||||
'background' => env('VUEXY_AVATAR_INITIALS_BACKGROUND', '#EBF4FF'),
|
|
||||||
'colors' => json_decode(env('VUEXY_AVATAR_INITIALS_COLORS', json_encode(['#3b82f6', '#808390', '#28c76f', '#ff4c51', '#ff9f43', '#00bad1', '#4b4b4b'])), true),
|
|
||||||
'font_size_ratio' => (float) env('VUEXY_AVATAR_INITIALS_FONT_SIZE_RATIO', 0.4),
|
|
||||||
'fallback_text' => env('VUEXY_AVATAR_INITIALS_FALLBACK_TEXT', 'NA'),
|
|
||||||
'cache' => [
|
|
||||||
'ttl' => (int) env('VUEXY_AVATAR_INITIALS_CACHE_TTL', 30 * 24 * 60),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'image' => [
|
|
||||||
'disk' => env('VUEXY_AVATAR_IMAGE_DISK', 'public'),
|
|
||||||
'directory' => env('VUEXY_AVATAR_IMAGE_DIRECTORY', 'profile-photos'),
|
|
||||||
'width' => (int) env('VUEXY_AVATAR_IMAGE_WIDTH', 512),
|
|
||||||
'height' => (int) env('VUEXY_AVATAR_IMAGE_HEIGHT', 512),
|
|
||||||
'fit_method' => env('VUEXY_AVATAR_IMAGE_FIT_METHOD', 'cover'),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
// Menú
|
|
||||||
'menu' => [
|
|
||||||
'cache' => [
|
|
||||||
'enabled' => (bool) env('VUEXY_MENU_CACHE_ENABLED', true),
|
|
||||||
'ttl' => (int) env('VUEXY_MENU_CACHE_TTL', 2 * 24 * 60),
|
|
||||||
],
|
|
||||||
'debug' => [
|
|
||||||
'show_broken_routers' => (bool) env('VUEXY_MENU_DEBUG_SHOW_BROKEN_ROUTES', false),
|
|
||||||
'show_disallowed_links' => (bool) env('VUEXY_MENU_DEBUG_SHOW_DISALLOWED_LINKS', false),
|
|
||||||
'show_hidden_items' => (bool) env('VUEXY_MENU_DEBUG_SHOW_HIDDEN_ITEMS', false),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
57
config/koneko_key_vault.php
Normal file
57
config/koneko_key_vault.php
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Vault Client Mode
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Define si este proyecto se comporta como "client", "server", o "both".
|
||||||
|
| Este valor se puede usar para omitir migraciones o inicializar módulos.
|
||||||
|
*/
|
||||||
|
'mode' => env('KONEKO_KEY_VAULT_MODE', 'client'), // client | server | both
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Cliente de Claves - Lectura local o remota
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
'client' => [
|
||||||
|
'driver' => env('KONEKO_KEY_VAULT_DRIVER', 'laravel'), // koneko_api | database | laravel
|
||||||
|
'connection' => env('KONEKO_KEY_VAULT_DB_CONNECTION', 'vault'),
|
||||||
|
'table' => env('KONEKO_KEY_VAULT_DB_TABLE', 'vault_client_keys'),
|
||||||
|
|
||||||
|
'project' => env('KONEKO_PROJECT_CODE', 'erp'),
|
||||||
|
'namespace' => env('KONEKO_KEY_VAULT_NAMESPACE', 'default'),
|
||||||
|
'client_id' => env('KONEKO_CLIENT_ID'),
|
||||||
|
|
||||||
|
// 🔐 Opción para lectura desde archivo plano en sistema
|
||||||
|
'key_path' => env('KONEKO_KEY_VAULT_CLIENT_KEY_PATH', '/etc/koneko/vault_value.key'),
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Servidor de Claves - Proyectos que gestionan claves de múltiples clientes
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
'server' => [
|
||||||
|
'enabled' => env('KONEKO_KEY_VAULT_SERVER_ENABLED', false),
|
||||||
|
'connection' => env('KONEKO_KEY_VAULT_SERVER_CONNECTION', 'vault'),
|
||||||
|
'table' => env('KONEKO_KEY_VAULT_SERVER_TABLE', 'vault_client_keys'),
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Drivers disponibles (por ejemplo, API externa Koneko)
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
'drivers' => [
|
||||||
|
'koneko_api' => [
|
||||||
|
'base_url' => env('KONEKO_VAULT_API_URL', 'https://vault.koneko.mx/api/v1/keys'),
|
||||||
|
'api_token' => env('KONEKO_VAULT_API_TOKEN'),
|
||||||
|
'timeout' => (int) env('KONEKO_VAULT_API_TIMEOUT', 3),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
16
config/koneko_key_vault_db.php
Normal file
16
config/koneko_key_vault_db.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'driver' => 'mysql',
|
||||||
|
'host' => env('KEYVAULT_DB_HOST', '127.0.0.1'),
|
||||||
|
'database' => env('KEYVAULT_DB_DATABASE', 'key_vault'),
|
||||||
|
'username' => env('KEYVAULT_DB_USERNAME', 'vault_user'),
|
||||||
|
'password' => env('KEYVAULT_DB_PASSWORD', 'secret'),
|
||||||
|
'charset' => 'utf8mb4',
|
||||||
|
'collation' => 'utf8mb4_unicode_ci',
|
||||||
|
'prefix' => env('KEYVAULT_DB_PREFIX', ''),
|
||||||
|
'strict' => true,
|
||||||
|
'engine' => null,
|
||||||
|
];
|
48
config/koneko_layout.php
Normal file
48
config/koneko_layout.php
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
// ================== 🌐 LAYOUT ==================
|
||||||
|
// Personalización de interfaz
|
||||||
|
'vuexy' => [
|
||||||
|
'myLayout' => 'horizontal', // Options[String]: vertical(default), horizontal
|
||||||
|
'myTheme' => 'theme-semi-dark', // Options[String]: theme-default(default), theme-bordered, theme-semi-dark
|
||||||
|
'myStyle' => 'light', // Options[String]: light(default), dark & system mode
|
||||||
|
'myRTLSupport' => false, // options[Boolean]: true(default), false // To provide RTLSupport or not
|
||||||
|
'myRTLMode' => false, // options[Boolean]: false(default), true // To set layout to RTL layout (myRTLSupport must be true for rtl mode)
|
||||||
|
'hasCustomizer' => true, // options[Boolean]: true(default), false // Display customizer or not THIS WILL REMOVE INCLUDED JS FILE. SO LOCAL STORAGE WON'T WORK
|
||||||
|
'displayCustomizer' => true, // options[Boolean]: true(default), false // Display customizer UI or not, THIS WON'T REMOVE INCLUDED JS FILE. SO LOCAL STORAGE WILL WORK
|
||||||
|
'contentLayout' => 'compact', // options[String]: 'compact', 'wide' (compact=container-xxl, wide=container-fluid)
|
||||||
|
'navbarType' => 'static', // options[String]: 'sticky', 'static', 'hidden' (Only for vertical Layout)
|
||||||
|
'footerFixed' => false, // options[Boolean]: false(default), true // Footer Fixed
|
||||||
|
'menuFixed' => false, // options[Boolean]: true(default), false // Layout(menu) Fixed (Only for vertical Layout)
|
||||||
|
'menuCollapsed' => true, // options[Boolean]: false(default), true // Show menu collapsed, (Only for vertical Layout)
|
||||||
|
'headerType' => 'static', // options[String]: 'static', 'fixed' (for horizontal layout only)
|
||||||
|
'showDropdownOnHover' => false, // true, false (for horizontal layout only)
|
||||||
|
'authViewMode' => 'cover', // Options[String]: cover(default), basic
|
||||||
|
'maxQuickLinks' => 12, // options[Integer]: 8(default), 8, 10, 12
|
||||||
|
'customizerControls' => [
|
||||||
|
'style',
|
||||||
|
'headerType',
|
||||||
|
'contentLayout',
|
||||||
|
'layoutCollapsed',
|
||||||
|
'layoutNavbarOptions',
|
||||||
|
'themes',
|
||||||
|
], // To show/hide customizer options
|
||||||
|
],
|
||||||
|
// 📋 Menú de Navegación
|
||||||
|
'menu' => [
|
||||||
|
'debug' => [
|
||||||
|
'show_broken_routes' => (bool) env('VUEXY_MENU_DEBUG_SHOW_BROKEN_ROUTES', false),
|
||||||
|
'show_disallowed_links'=> (bool) env('VUEXY_MENU_DEBUG_SHOW_DISALLOWED_LINKS', false),
|
||||||
|
'show_hidden_items' => (bool) env('VUEXY_MENU_DEBUG_SHOW_HIDDEN_ITEMS', false),
|
||||||
|
],
|
||||||
|
'cache' => [
|
||||||
|
'enabled' => (bool) env('VUEXY_MENU_CACHE_ENABLED', true),
|
||||||
|
'ttl' => (int) env('VUEXY_MENU_CACHE_TTL', 2 * 24 * 60),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'cache' => [
|
||||||
|
'enabled' => (bool) env('VUEXY_CACHE_ENABLED', true),
|
||||||
|
'ttl' => (int) env('VUEXY_CACHE_TTL', 2 * 24 * 60),
|
||||||
|
],
|
||||||
|
];
|
59
config/koneko_security.php
Normal file
59
config/koneko_security.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
// ================== 🔒 SEGURIDAD ==================
|
||||||
|
// 🔐 HTTPS y Proxies
|
||||||
|
'https' => [
|
||||||
|
'force' => (bool) env('FORCE_HTTPS', false),
|
||||||
|
],
|
||||||
|
'proxies' => [
|
||||||
|
'enabled' => (bool) env('TRUST_PROXY', false),
|
||||||
|
'ips' => env('TRUST_PROXY_IPS', '*'),
|
||||||
|
],
|
||||||
|
|
||||||
|
// 🗝️ Key Vault & Gestión de Claves
|
||||||
|
'key_vault' => [
|
||||||
|
// Namespace por defecto y cliente global (si aplica)
|
||||||
|
'default_namespace' => env('KONEKO_KEY_VAULT_NAMESPACE', 'default'),
|
||||||
|
'default_project' => env('KONEKO_PROJECT_CODE', 'erp'),
|
||||||
|
'default_client_id' => env('KONEKO_CLIENT_ID'),
|
||||||
|
|
||||||
|
|
||||||
|
// 🔑 Cliente que accede a claves de un servidor remoto o local
|
||||||
|
'client' => [
|
||||||
|
'driver' => env('KONEKO_KEY_VAULT_DRIVER', 'database'), // koneko_api, database, laravel
|
||||||
|
'connection' => env('KONEKO_KEY_VAULT_DB_CONNECTION', 'vault'),
|
||||||
|
'table' => env('KONEKO_KEY_VAULT_DB_TABLE', 'vault_client_keys'),
|
||||||
|
'project' => env('KONEKO_PROJECT_CODE', 'erp'),
|
||||||
|
'namespace' => env('KONEKO_KEY_VAULT_NAMESPACE', 'default'),
|
||||||
|
'client_id' => env('KONEKO_CLIENT_ID'),
|
||||||
|
],
|
||||||
|
|
||||||
|
// 🗃️ Conexión y configuración del servidor de claves (solo si actúa como vault)
|
||||||
|
'server' => [
|
||||||
|
'connection' => 'vault', // conexión que administra la tabla físicamente
|
||||||
|
'table' => 'vault_client_keys',
|
||||||
|
],
|
||||||
|
|
||||||
|
|
||||||
|
'drivers' => [
|
||||||
|
// Laravel Default Encryption (APP_KEY)
|
||||||
|
'laravel' => [
|
||||||
|
'key' => env('APP_KEY'),
|
||||||
|
'algorithm' => 'AES-256-CBC',
|
||||||
|
],
|
||||||
|
// Second DB Configuration (Requires separate connection)
|
||||||
|
'database' => [
|
||||||
|
'connection' => env('KONEKO_KEY_VAULT_DB_CONNECTION', 'vault'),
|
||||||
|
'table' => env('KONEKO_KEY_VAULT_DB_TABLE', 'vault_keys'),
|
||||||
|
'algorithm' => env('KONEKO_KEY_VAULT_DB_ALGORITHM', 'AES-256-CBC'),
|
||||||
|
],
|
||||||
|
// External Go Microservice
|
||||||
|
'koneko_api' => [
|
||||||
|
'base_url' => env('KONEKO_KEY_VAULT_SERVICE_URL'),
|
||||||
|
'api_token' => env('KONEKO_KEY_VAULT_SERVICE_TOKEN'),
|
||||||
|
'timeout' => (int) env('KONEKO_KEY_VAULT_SERVICE_TIMEOUT', 5),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
39
config/koneko_ui.php
Normal file
39
config/koneko_ui.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
// ================== 👤 UI / AVATAR ==================
|
||||||
|
'avatar' => [
|
||||||
|
// 🔠 Avatares por Iniciales
|
||||||
|
'initials' => [
|
||||||
|
'max_length' => (int) env('VUEXY_AVATAR_INITIALS_MAX_LENGTH', 2),
|
||||||
|
'disk' => env('VUEXY_AVATAR_INITIALS_DISK', 'public'),
|
||||||
|
'directory' => env('VUEXY_AVATAR_INITIALS_DIRECTORY', 'initial-avatars'),
|
||||||
|
'size' => (int) env('VUEXY_AVATAR_INITIALS_SIZE', 512),
|
||||||
|
'background' => env('VUEXY_AVATAR_INITIALS_BACKGROUND', '#EBF4FF'),
|
||||||
|
'colors' => json_decode(env('VUEXY_AVATAR_INITIALS_COLORS', json_encode([
|
||||||
|
'#3b82f6', '#808390', '#28c76f', '#ff4c51',
|
||||||
|
'#ff9f43', '#00bad1', '#4b4b4b'
|
||||||
|
])), true),
|
||||||
|
'font_size_ratio' => (float) env('VUEXY_AVATAR_INITIALS_FONT_SIZE_RATIO', 0.4),
|
||||||
|
'fallback_text' => env('VUEXY_AVATAR_INITIALS_FALLBACK_TEXT', 'NA'),
|
||||||
|
|
||||||
|
// 🧹 Mantenimiento y Depuración de Avatares
|
||||||
|
'maintenance' => [
|
||||||
|
'enabled' => (bool) env('VUEXY_AVATAR_CLEANUP_ENABLED', true),
|
||||||
|
'ttl_days' => (int) env('VUEXY_AVATAR_CLEANUP_TTL_DAYS', 30), // Días antes de considerar obsoletos los avatares
|
||||||
|
'cleanup_cron' => env('VUEXY_AVATAR_CLEANUP_CRON', '0 3 * * *'), // Hora de ejecución del Job de limpieza (por defecto a las 3 AM)
|
||||||
|
'max_batch_size' => (int) env('VUEXY_AVATAR_CLEANUP_MAX_BATCH', 500), // Máximo de archivos a procesar por ejecución
|
||||||
|
'log_deletions' => (bool) env('VUEXY_AVATAR_CLEANUP_LOG', true), // Registrar o no las eliminaciones
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
// 📸 Avatares por Imagen
|
||||||
|
'image' => [
|
||||||
|
'disk' => env('VUEXY_AVATAR_IMAGE_DISK', 'public'),
|
||||||
|
'directory' => env('VUEXY_AVATAR_IMAGE_DIRECTORY', 'profile-photos'),
|
||||||
|
'width' => (int) env('VUEXY_AVATAR_IMAGE_WIDTH', 512),
|
||||||
|
'height' => (int) env('VUEXY_AVATAR_IMAGE_HEIGHT', 512),
|
||||||
|
'fit_method' => env('VUEXY_AVATAR_IMAGE_FIT_METHOD', 'cover'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
@ -481,15 +481,16 @@ return [
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'_extra_quicklinks' => [
|
'_extra' => [
|
||||||
'Inicio' => [
|
'_quicklinks' => [
|
||||||
'icon' => 'ti ti-home',
|
'Inicio' => [
|
||||||
'route' => 'admin.core.pages.home.index',
|
'icon' => 'ti ti-home',
|
||||||
'can' => 'admin.core.pages.home.view',
|
'route' => 'admin.core.pages.home.index',
|
||||||
],
|
],
|
||||||
'Mi perfil' => [
|
'Mi perfil' => [
|
||||||
'icon' => 'ti ti-user-circle',
|
'icon' => 'ti ti-user-circle',
|
||||||
'route' => 'admin.users.profile',
|
'route' => 'admin.users.profile',
|
||||||
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
$keyVault = config_m()->setGroup('key_vault');
|
||||||
|
|
||||||
|
$mode = $keyVault->get('mode', 'client');
|
||||||
|
$driver = $keyVault->get('client.driver', 'database');
|
||||||
|
$table = $keyVault->get('server.table', 'vault_keys');
|
||||||
|
$conn = $keyVault->get('server.connection', 'vault');
|
||||||
|
|
||||||
|
$isServer = in_array($mode, ['server', 'both']);
|
||||||
|
$isDatabaseDriver = $driver === 'database';
|
||||||
|
|
||||||
|
if (!$isServer) {
|
||||||
|
echo "\n ⏩ Skipping: vault_keys not needed for mode '{$mode}'.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$isDatabaseDriver) {
|
||||||
|
echo "\n ⏩ Skipping: vault_keys not needed for driver '{$driver}'.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Schema::connection($conn)->hasTable($table)) {
|
||||||
|
echo "\n ⏩ Table {$table} already exists on connection '{$conn}'.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crear la tabla
|
||||||
|
Schema::connection($conn)->create($table, function (Blueprint $table) {
|
||||||
|
$table->bigIncrements('id');
|
||||||
|
|
||||||
|
$table->string('alias', 64)->unique()->index();
|
||||||
|
$table->string('owner_project', 64)->index();
|
||||||
|
$table->string('namespace', 32)->default('core')->index();
|
||||||
|
|
||||||
|
$table->string('algorithm', 32)->default('AES-256-CBC');
|
||||||
|
$table->binary('key_material');
|
||||||
|
$table->boolean('is_active')->default(true);
|
||||||
|
$table->boolean('is_sensitive')->default(true);
|
||||||
|
|
||||||
|
$table->timestamp('rotated_at')->nullable();
|
||||||
|
$table->unsignedInteger('rotation_count')->default(0);
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('created_by')->nullable()->index();
|
||||||
|
$table->unsignedBigInteger('updated_by')->nullable()->index();
|
||||||
|
$table->unsignedBigInteger('deleted_by')->nullable()->index();
|
||||||
|
|
||||||
|
$table->timestamps();
|
||||||
|
$table->softDeletes();
|
||||||
|
|
||||||
|
$table->index(['owner_project', 'namespace', 'is_active'], 'idx_full_context');
|
||||||
|
});
|
||||||
|
|
||||||
|
info("✅ Created table `{$table}` in connection [{$conn}].");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$keyVault = config_m()->setGroup('key_vault');
|
||||||
|
|
||||||
|
$driver = $keyVault->get('client.driver', 'database');
|
||||||
|
$table = $keyVault->get('server.table', 'vault_keys');
|
||||||
|
$conn = $keyVault->get('server.connection', 'vault');
|
||||||
|
|
||||||
|
if ($driver === 'database' && Schema::connection($conn)->hasTable($table)) {
|
||||||
|
Schema::connection($conn)->dropIfExists($table);
|
||||||
|
info("🗑️ Dropped table `{$table}` from connection [{$conn}].");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -1,57 +1,70 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyAdmin\Database\Migrations;
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
use Illuminate\Database\Migrations\Migration;
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
|
|
||||||
return new class extends Migration
|
return new class extends Migration {
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the migrations.
|
|
||||||
*/
|
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('settings', function (Blueprint $table) {
|
Schema::create('settings', function (Blueprint $table) {
|
||||||
$table->mediumIncrements('id');
|
$table->integerIncrements('id');
|
||||||
|
|
||||||
$table->string('key')->index(); // Clave del setting
|
// Clave identificadora única para uso directo en Redis u otras estructuras rápidas
|
||||||
|
$table->string('key')->unique();
|
||||||
|
|
||||||
$table->string('namespace', 8)->index(); // Namespace del setting
|
// Contexto completo del setting
|
||||||
$table->string('environment', 7)->default('prod')->index(); // Entorno de aplicación (prod, dev, test, staging), permite sobrescribir valores según ambiente.
|
$table->string('namespace', 8)->index(); // Ej. 'koneko'
|
||||||
$table->string('scope', 6)->default('global')->index(); // Define el alcance: global, tenant, branch, user, etc. Útil en arquitecturas multicliente.
|
$table->string('environment', 7)->default('prod')->index(); // prod, dev, staging, test
|
||||||
|
$table->string('component', 16)->index(); // Ej. 'vuexy-admin'
|
||||||
|
$table->string('module')->nullable()->index(); // composerName del módulo
|
||||||
|
|
||||||
$table->string('component', 16)->index(); // Nombre de Componente o proyecto
|
$table->string('scope', 16)->nullable()->index(); // user, branch, tenant, etc.
|
||||||
$table->string('module')->nullable()->index(); // composerName de módulo Autocalculado
|
$table->unsignedMediumInteger('scope_id')->nullable()->index(); // ID vinculado al scope (ej. user_id, branch_id)
|
||||||
$table->string('group', 16)->index(); // Grupo de configuraciones
|
|
||||||
$table->string('sub_group', 16)->index(); // Sub grupo de configuraciones
|
|
||||||
$table->string('key_name', 24)->index(); // Nombre de la clave de configuraciones
|
|
||||||
$table->unsignedMediumInteger('user_id')->nullable()->index(); // Usuario (null para globales)
|
|
||||||
|
|
||||||
$table->boolean('is_system')->default(false)->index(); // Indica si es un setting de sistema
|
$table->string('group', 16)->index(); // Grupo funcional (ej. layout, ui, behavior)
|
||||||
$table->boolean('is_encrypted')->default(false)->index(); // Si el valor está cifrado (para secretos, tokens, passwords).
|
$table->string('section', 16)->default('default')->index(); // Bloque intermedio semántico
|
||||||
$table->boolean('is_sensitive')->default(false)->index(); // Marca datos sensibles (ej. datos personales, claves API). Puede ocultarse en UI o logs.
|
$table->string('sub_group', 16)->default('default')->index(); // Subgrupo semántico opcional
|
||||||
$table->boolean('is_editable')->default(true)->index(); // Permite o bloquea edición desde la UI (útil para settings de solo lectura).
|
$table->string('key_name', 24)->index(); // Sub-subgrupo lógico o tipo de configuración
|
||||||
$table->boolean('is_active')->default(true)->index(); // Permite activar/desactivar la aplicación de un setting sin eliminarlo.
|
|
||||||
|
|
||||||
$table->string('encryption_key', 64)->nullable()->index(); // Identificador de la clave usada (ej. 'ssl_cert_2025')
|
// Flags operativos
|
||||||
$table->string('encryption_algorithm', 16)->nullable(); // Ej. 'AES-256-CBC'
|
$table->boolean('is_system')->default(false)->index();
|
||||||
$table->timestamp('encryption_rotated_at')->nullable(); // Fecha de última rotación de clave
|
$table->boolean('is_sensitive')->default(false)->index();
|
||||||
|
$table->boolean('is_file')->default(false)->index();
|
||||||
|
$table->boolean('is_encrypted')->default(false)->index();
|
||||||
|
$table->boolean('is_config')->default(false)->index();
|
||||||
|
$table->boolean('is_editable')->default(true)->index();
|
||||||
|
$table->boolean('is_track_usage')->default(false)->index();
|
||||||
|
$table->boolean('is_should_cache')->default(true)->index();
|
||||||
|
$table->boolean('is_active')->default(true)->index();
|
||||||
|
|
||||||
|
// Metadata para archivos y cifrado
|
||||||
|
$table->string('mime_type', 50)->nullable();
|
||||||
|
$table->string('file_name')->nullable();
|
||||||
|
$table->string('encryption_algorithm', 16)->nullable();
|
||||||
|
$table->string('encryption_key', 64)->nullable()->index();
|
||||||
|
$table->timestamp('encryption_rotated_at')->nullable();
|
||||||
|
|
||||||
|
// Expiración del setting o su valor cacheado
|
||||||
|
$table->timestamp('expires_at')->nullable();
|
||||||
|
$table->unsignedInteger('usage_count')->nullable();
|
||||||
|
$table->timestamp('last_used_at')->nullable();
|
||||||
|
$table->unsignedSmallInteger('cache_ttl')->nullable();
|
||||||
|
$table->timestamp('cache_expires_at')->nullable();
|
||||||
|
|
||||||
|
// Descripciones para UI
|
||||||
$table->string('description')->nullable();
|
$table->string('description')->nullable();
|
||||||
$table->string('hint')->nullable();
|
$table->string('hint')->nullable();
|
||||||
$table->timestamp('last_used_at')->nullable();
|
|
||||||
$table->integer('usage_count')->default(0)->index();
|
|
||||||
|
|
||||||
// Valores segmentados por tipo para mejor rendimiento
|
// Valores segmentados
|
||||||
$table->string('value_string')->nullable();
|
$table->string('value_string')->nullable();
|
||||||
$table->integer('value_integer')->nullable();
|
$table->integer('value_integer')->nullable();
|
||||||
$table->boolean('value_boolean')->nullable();
|
$table->boolean('value_boolean')->nullable();
|
||||||
$table->float('value_float', 16, 8)->nullable();
|
$table->float('value_float', 16, 8)->nullable();
|
||||||
$table->text('value_text')->nullable();
|
$table->text('value_text')->nullable();
|
||||||
$table->binary('value_binary')->nullable();
|
$table->binary('value_binary')->nullable();
|
||||||
$table->string('mime_type', 50)->nullable();
|
|
||||||
$table->string('file_name')->nullable();
|
|
||||||
|
|
||||||
// Auditoría
|
// Auditoría
|
||||||
$table->unsignedMediumInteger('created_by')->nullable()->index();
|
$table->unsignedMediumInteger('created_by')->nullable()->index();
|
||||||
@ -59,50 +72,43 @@ return new class extends Migration
|
|||||||
$table->unsignedMediumInteger('deleted_by')->nullable()->index();
|
$table->unsignedMediumInteger('deleted_by')->nullable()->index();
|
||||||
|
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
$table->softDeletes(); // THIS ONE
|
$table->softDeletes();
|
||||||
|
|
||||||
// Índice de Unicidad Principal (para consultas y updates rápidos)
|
// 📌 Clave única por contexto semántico-lógico
|
||||||
$table->unique(
|
$table->unique([
|
||||||
['namespace', 'environment', 'scope', 'component', 'group', 'sub_group', 'key_name', 'user_id'],
|
'namespace',
|
||||||
'uniq_settings_full_context'
|
'environment',
|
||||||
);
|
'component',
|
||||||
|
'scope',
|
||||||
|
'scope_id',
|
||||||
|
'group',
|
||||||
|
'section',
|
||||||
|
'sub_group',
|
||||||
|
'key_name'
|
||||||
|
], 'uniq_settings_full_context');
|
||||||
|
|
||||||
// Búsqueda rápida por componente
|
// 📈 Índices especializados para consultas
|
||||||
$table->index(['namespace', 'component'], 'idx_settings_ns_component');
|
$table->index(['namespace', 'environment', 'component'], 'idx_ns_env_component');
|
||||||
|
$table->index(['namespace', 'environment', 'component', 'is_active'], 'idx_ns_is_active');
|
||||||
|
$table->index(['namespace', 'environment', 'component', 'is_system'], 'idx_ns_is_system');
|
||||||
|
$table->index(['namespace', 'environment', 'component', 'is_config'], 'idx_ns_is_config');
|
||||||
|
$table->index(['namespace', 'environment', 'component', 'is_encrypted'], 'idx_ns_is_encrypted');
|
||||||
|
$table->index(['namespace', 'environment', 'component', 'is_file'], 'idx_ns_is_file');
|
||||||
|
$table->index(['namespace', 'environment', 'component', 'is_editable'], 'idx_ns_is_editable');
|
||||||
|
$table->index(['namespace', 'environment', 'component', 'is_track_usage'], 'idx_ns_is_track_usage');
|
||||||
|
$table->index(['namespace', 'environment', 'component', 'is_should_cache'], 'idx_ns_is_should_cache');
|
||||||
|
$table->index(['namespace', 'environment', 'component', 'group', 'section', 'sub_group'], 'idx_ns_env_comp_group_sect_subg');
|
||||||
|
$table->index(['namespace', 'environment', 'component', 'group', 'section', 'sub_group', 'scope', 'scope_id'], 'idx_ns_env_comp_group_sect_subg_scope');
|
||||||
|
|
||||||
// Listar grupos de un componente por scope
|
// 🔒 Relaciones de auditoría
|
||||||
$table->index(['namespace', 'scope', 'component', 'group'], 'idx_settings_ns_scope_component_group');
|
|
||||||
|
|
||||||
// Listar subgrupos por grupo y componente en un scope
|
|
||||||
$table->index(['namespace', 'scope', 'component', 'group', 'sub_group'], 'idx_settings_ns_scope_component_sg');
|
|
||||||
|
|
||||||
// Consultas por entorno y usuario
|
|
||||||
$table->index(['namespace', 'environment', 'user_id'], 'idx_settings_ns_env_user');
|
|
||||||
|
|
||||||
// Consultas por scope y usuario
|
|
||||||
$table->index(['namespace', 'scope', 'user_id'], 'idx_settings_ns_scope_user');
|
|
||||||
|
|
||||||
// Consultas por estado de actividad o sistema
|
|
||||||
$table->index(['namespace', 'is_active'], 'idx_settings_ns_is_active');
|
|
||||||
$table->index(['namespace', 'is_system'], 'idx_settings_ns_is_system');
|
|
||||||
|
|
||||||
// Consultas por estado de cifrado y usuario
|
|
||||||
$table->index(['namespace', 'is_encrypted', 'user_id'], 'idx_settings_encrypted_user');
|
|
||||||
|
|
||||||
// Relaciones
|
|
||||||
$table->foreign('user_id')->references('id')->on('users')->restrictOnDelete();
|
|
||||||
$table->foreign('created_by')->references('id')->on('users')->restrictOnDelete();
|
$table->foreign('created_by')->references('id')->on('users')->restrictOnDelete();
|
||||||
$table->foreign('updated_by')->references('id')->on('users')->restrictOnDelete();
|
$table->foreign('updated_by')->references('id')->on('users')->restrictOnDelete();
|
||||||
$table->foreign('deleted_by')->references('id')->on('users')->restrictOnDelete();
|
$table->foreign('deleted_by')->references('id')->on('users')->restrictOnDelete();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
Schema::dropIfExists('settings');
|
Schema::dropIfExists('settings');
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the migrations.
|
|
||||||
*/
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
Schema::connection('vault')->create('vault_keys', function (Blueprint $table) {
|
|
||||||
$table->bigIncrements('id');
|
|
||||||
|
|
||||||
// Contexto de clave
|
|
||||||
$table->string('alias', 64)->unique()->index();
|
|
||||||
$table->string('owner_project', 64)->index();
|
|
||||||
$table->string('environment', 10)->default('prod')->index(); // prod, dev, staging
|
|
||||||
$table->string('namespace', 32)->default('core')->index();
|
|
||||||
$table->string('scope', 16)->default('global')->index(); // global, tenant, user
|
|
||||||
|
|
||||||
// Datos de la clave
|
|
||||||
$table->string('algorithm', 32)->default('AES-256-CBC');
|
|
||||||
$table->binary('key_material');
|
|
||||||
$table->boolean('is_active')->default(true);
|
|
||||||
$table->boolean('is_sensitive')->default(true);
|
|
||||||
|
|
||||||
// Control de rotación
|
|
||||||
$table->timestamp('rotated_at')->nullable();
|
|
||||||
$table->unsignedInteger('rotation_count')->default(0);
|
|
||||||
|
|
||||||
// Auditoría
|
|
||||||
$table->unsignedBigInteger('created_by')->nullable()->index();
|
|
||||||
$table->unsignedBigInteger('updated_by')->nullable()->index();
|
|
||||||
$table->unsignedBigInteger('deleted_by')->nullable()->index();
|
|
||||||
|
|
||||||
$table->timestamps();
|
|
||||||
$table->softDeletes();
|
|
||||||
|
|
||||||
// Índices adicionales
|
|
||||||
$table->index(['owner_project', 'environment', 'namespace', 'scope', 'is_active'], 'idx_full_context');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::connection('vault')->dropIfExists('settings');
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
@ -1,68 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="{{ dynamic_lang }}" prefix="og: http://ogp.me/ns#">
|
|
||||||
<head>
|
|
||||||
<!-- Metadatos Esenciales -->
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
|
|
||||||
<!-- Title Tag Optimizado -->
|
|
||||||
<title>{{ page_title }} | {{ site_name }}</title>
|
|
||||||
|
|
||||||
<!-- Meta Description Dinámica -->
|
|
||||||
<meta name="description" content="{{ meta_description|truncate:160 }}">
|
|
||||||
|
|
||||||
<!-- Open Graph / Facebook -->
|
|
||||||
<meta property="og:type" content="{{ og_type }}">
|
|
||||||
<meta property="og:url" content="{{ canonical_url }}">
|
|
||||||
<meta property="og:title" content="{{ og_title|truncate:60 }}">
|
|
||||||
<meta property="og:description" content="{{ og_description|truncate:160 }}">
|
|
||||||
<meta property="og:image" content="{{ og_image_url }}">
|
|
||||||
<meta property="og:site_name" content="{{ site_name }}">
|
|
||||||
|
|
||||||
<!-- Twitter Card -->
|
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
|
||||||
<meta name="twitter:creator" content="{{ twitter_handle }}">
|
|
||||||
<!-- ... otros meta twitter ... -->
|
|
||||||
|
|
||||||
<!-- Robots Directives -->
|
|
||||||
<meta name="robots" content="{{ index_status }}, {{ follow_status }}, max-image-preview:large">
|
|
||||||
|
|
||||||
<!-- Idioma y Geolocalización -->
|
|
||||||
<meta name="language" content="{{ main_language }}">
|
|
||||||
<meta name="geo.region" content="{{ geo_region }}">
|
|
||||||
<meta name="geo.placename" content="{{ geo_placename }}">
|
|
||||||
|
|
||||||
<!-- Enlaces Canónicos y Alternativos -->
|
|
||||||
<link rel="canonical" href="{{ canonical_url }}">
|
|
||||||
{% for alternate in alternate_langs %}
|
|
||||||
<link rel="alternate" hreflang="{{ alternate.code }}" href="{{ alternate.url }}">
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<!-- Preconexiones y Preloads -->
|
|
||||||
<link rel="preconnect" href="https://www.googletagmanager.com">
|
|
||||||
<link rel="dns-prefetch" href="//fonts.googleapis.com">
|
|
||||||
|
|
||||||
<!-- Favicon Moderno (SVG + PNG fallback) -->
|
|
||||||
<link rel="icon" href="/assets/favicon.svg" type="image/svg+xml">
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
|
||||||
|
|
||||||
<!-- CSS Crítico Inline -->
|
|
||||||
<style>/* CSS mínimo para above-the-fold */</style>
|
|
||||||
|
|
||||||
<!-- Schema.org JSON-LD -->
|
|
||||||
<script type="application/ld+json">
|
|
||||||
{
|
|
||||||
"@context": "https://schema.org",
|
|
||||||
"@type": "WebSite",
|
|
||||||
"name": "{{ site_name }}",
|
|
||||||
"url": "{{ base_url }}",
|
|
||||||
"potentialAction": {
|
|
||||||
"@type": "SearchAction",
|
|
||||||
"target": "{{ search_url }}?q={search_term_string}",
|
|
||||||
"query-input": "required name=search_term_string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body itemscope itemtype="http://schema.org/WebPage">
|
|
@ -1,12 +1,12 @@
|
|||||||
# Koneko ERP - Cache Helper Guide
|
# Koneko ERP - Cache Helper Guide
|
||||||
|
|
||||||
> ✨ Esta guía detalla el uso correcto del sistema de cache en Koneko ERP, basado completamente en el helper `cache_manager()`, sin necesidad de interactuar con las clases internas como `KonekoCacheManager` o `LaravelCacheManager`.
|
> ✨ Esta guía detalla el uso correcto del sistema de cache en Koneko ERP, basado completamente en el helper `cache_m()`, sin necesidad de interactuar con las clases internas como `KonekoCacheManager` o `LaravelCacheManager`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔎 Filosofía
|
## 🔎 Filosofía
|
||||||
|
|
||||||
* Toda interacción de componentes con el sistema de cache debe realizarse exclusivamente mediante el helper `cache_manager()`.
|
* Toda interacción de componentes con el sistema de cache debe realizarse exclusivamente mediante el helper `cache_m()`.
|
||||||
* Las clases internas son consideradas **@internal**, y no deben ser accedidas directamente por desarrolladores de componentes.
|
* Las clases internas son consideradas **@internal**, y no deben ser accedidas directamente por desarrolladores de componentes.
|
||||||
* El sistema permite configuración jerárquica basada en namespace del componente, grupo lógico de datos y claves individuales.
|
* El sistema permite configuración jerárquica basada en namespace del componente, grupo lógico de datos y claves individuales.
|
||||||
|
|
||||||
@ -15,15 +15,15 @@
|
|||||||
## 🔍 Sintaxis del Helper
|
## 🔍 Sintaxis del Helper
|
||||||
|
|
||||||
```php
|
```php
|
||||||
cache_manager(string $component = 'admin', string $group = 'cache')
|
cache_m(string $component = 'admin', string $group = 'cache')
|
||||||
```
|
```
|
||||||
|
|
||||||
Retorna una instancia segura del gestor para el componente y grupo indicados. Ejemplos:
|
Retorna una instancia segura del gestor para el componente y grupo indicados. Ejemplos:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
cache_manager('admin', 'avatar')->enabled();
|
cache_m('admin', 'avatar')->enabled();
|
||||||
cache_manager('website', 'menu')->ttl();
|
cache_m('website', 'menu')->ttl();
|
||||||
cache_manager('website', 'html')->flush();
|
cache_m('website', 'html')->flush();
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -60,13 +60,13 @@ Esto permite granularidad sin perder coherencia global.
|
|||||||
### Validar TTL efectivo
|
### Validar TTL efectivo
|
||||||
|
|
||||||
```php
|
```php
|
||||||
$ttl = cache_manager('website', 'menu')->ttl();
|
$ttl = cache_m('website', 'menu')->ttl();
|
||||||
```
|
```
|
||||||
|
|
||||||
### Verificar si está habilitado
|
### Verificar si está habilitado
|
||||||
|
|
||||||
```php
|
```php
|
||||||
if (cache_manager('admin', 'avatar')->enabled()) {
|
if (cache_m('admin', 'avatar')->enabled()) {
|
||||||
// Proceder con cache
|
// Proceder con cache
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -74,7 +74,7 @@ if (cache_manager('admin', 'avatar')->enabled()) {
|
|||||||
### Limpiar cache con soporte para etiquetas
|
### Limpiar cache con soporte para etiquetas
|
||||||
|
|
||||||
```php
|
```php
|
||||||
cache_manager('website', 'html')->flush();
|
cache_m('website', 'html')->flush();
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -112,6 +112,6 @@ El comando mostrará información relevante para depuración sin exponer clases
|
|||||||
|
|
||||||
## 🌟 Conclusión
|
## 🌟 Conclusión
|
||||||
|
|
||||||
Este sistema garantiza modularidad, extensibilidad y seguridad. El helper `cache_manager()` es la única puerta de entrada para desarrolladores y debe usarse exclusivamente para mantener la integridad del ecosistema.
|
Este sistema garantiza modularidad, extensibilidad y seguridad. El helper `cache_m()` es la única puerta de entrada para desarrolladores y debe usarse exclusivamente para mantener la integridad del ecosistema.
|
||||||
|
|
||||||
> ✅ Si necesitas agregar un nuevo grupo de cache, simplemente define su configuración y comienza a usar el helper, sin necesidad de modificar clases o contratos.
|
> ✅ Si necesitas agregar un nuevo grupo de cache, simplemente define su configuración y comienza a usar el helper, sin necesidad de modificar clases o contratos.
|
||||||
|
@ -9,7 +9,7 @@ Este documento define las reglas de exposición y acceso para los servicios téc
|
|||||||
| Clase Técnica | Expuesta al Usuario | Acceso Recomendado | Notas |
|
| Clase Técnica | Expuesta al Usuario | Acceso Recomendado | Notas |
|
||||||
| --------------------------------- | ------------------- | -------------------------- | ---------------------------------------------------------------- |
|
| --------------------------------- | ------------------- | -------------------------- | ---------------------------------------------------------------- |
|
||||||
| `KonekoSettingManager` | ❌ No | `settings()` helper | Configuración modular con soporte de namespaces. |
|
| `KonekoSettingManager` | ❌ No | `settings()` helper | Configuración modular con soporte de namespaces. |
|
||||||
| `KonekoCacheManager` | ❌ No | `cache_manager()` helper | Acceso al sistema de cache multi-driver y con TTL configurables. |
|
| `KonekoCacheManager` | ❌ No | `cache_m()` helper | Acceso al sistema de cache multi-driver y con TTL configurables. |
|
||||||
| `KonekoSystemLogger` | ❌ No | `log_system()` helper | Logger morfable con niveles y contexto extendido. |
|
| `KonekoSystemLogger` | ❌ No | `log_system()` helper | Logger morfable con niveles y contexto extendido. |
|
||||||
| `KonekoSecurityLogger` | ❌ No | `log_security()` helper | Eventos de seguridad con GeoIP y proxy detection. |
|
| `KonekoSecurityLogger` | ❌ No | `log_security()` helper | Eventos de seguridad con GeoIP y proxy detection. |
|
||||||
| `KonekoUserInteractionLogger` | ❌ No | `log_interaction()` helper | Auditoría de componentes y acciones sensibles. |
|
| `KonekoUserInteractionLogger` | ❌ No | `log_interaction()` helper | Auditoría de componentes y acciones sensibles. |
|
||||||
@ -28,7 +28,7 @@ Este documento define las reglas de exposición y acceso para los servicios téc
|
|||||||
## 💡 Buenas prácticas para desarrolladores
|
## 💡 Buenas prácticas para desarrolladores
|
||||||
|
|
||||||
* Usa `settings()` para acceder o escribir configuraciones modulares.
|
* Usa `settings()` para acceder o escribir configuraciones modulares.
|
||||||
* Usa `cache_manager()` para obtener TTL, flush o debug por componente.
|
* Usa `cache_m()` para obtener TTL, flush o debug por componente.
|
||||||
* Usa `log_system()` para registrar eventos de sistema de forma morfable.
|
* Usa `log_system()` para registrar eventos de sistema de forma morfable.
|
||||||
* Usa `log_security()` para eventos como logins fallidos o IP sospechosas.
|
* Usa `log_security()` para eventos como logins fallidos o IP sospechosas.
|
||||||
* Usa `log_interaction()` para acciones en Livewire, eventos UI o tracking avanzado.
|
* Usa `log_interaction()` para acciones en Livewire, eventos UI o tracking avanzado.
|
||||||
@ -40,7 +40,7 @@ Este documento define las reglas de exposición y acceso para los servicios téc
|
|||||||
```php
|
```php
|
||||||
// Correcto
|
// Correcto
|
||||||
settings()->in('website')->get('general.site_name');
|
settings()->in('website')->get('general.site_name');
|
||||||
cache_manager('admin', 'menu')->ttl();
|
cache_m('admin', 'menu')->ttl();
|
||||||
log_system('info', 'Menú regenerado');
|
log_system('info', 'Menú regenerado');
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ El evento registrado se almacena en la tabla `security_events`, con campos como:
|
|||||||
Si tienes habilitado el trait `HasGeolocation`, el sistema hace *GeoIP Lookup* por IP:
|
Si tienes habilitado el trait `HasGeolocation`, el sistema hace *GeoIP Lookup* por IP:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
use Koneko\VuexyAdmin\Support\Traits\Helpers\HasGeolocation;
|
use Koneko\VuexyAdmin\Support\Traits\Geolocation\HasGeolocation;
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
18
resources/assets/js/utils/favicon.js
Normal file
18
resources/assets/js/utils/favicon.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
window.faviconManager = {
|
||||||
|
set(src) {
|
||||||
|
let link = document.querySelector("link[rel~='icon']");
|
||||||
|
if (!link) {
|
||||||
|
link = document.createElement("link");
|
||||||
|
link.rel = "icon";
|
||||||
|
document.head.appendChild(link);
|
||||||
|
}
|
||||||
|
link.href = src;
|
||||||
|
},
|
||||||
|
flash(src, duration = 1500) {
|
||||||
|
const original = document.querySelector("link[rel~='icon']")?.href;
|
||||||
|
this.set(src);
|
||||||
|
if (original) {
|
||||||
|
setTimeout(() => this.set(original), duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -1,7 +1,7 @@
|
|||||||
@import 'tailwindcss/base';
|
@import 'tailwindcss/base';
|
||||||
@import 'tailwindcss/components';
|
@import 'tailwindcss/components';
|
||||||
@import 'tailwindcss/utilities';
|
@import 'tailwindcss/utilities';
|
||||||
$ti-font-path: '/public/build/fonts/tabler';
|
$ti-font-path: '/public/build/webfonts/tabler';
|
||||||
@import "@tabler/icons-webfont/dist/tabler-icons.scss";
|
@import "@tabler/icons-webfont/dist/tabler-icons.scss";
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<link href="https://fonts.googleapis.com/css2?family=Public+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Public+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
@vite([
|
@vite([
|
||||||
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/fonts/fontawesome.scss',
|
//'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/fonts/fontawesome.scss',
|
||||||
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/node-waves/node-waves.scss',
|
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/node-waves/node-waves.scss',
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
@php
|
||||||
|
use Koneko\VuexyAdmin\Models\User;
|
||||||
|
|
||||||
|
$maxQuickLinks = config_m()->get('layout.vuexy.maxQuickLinks', 8);
|
||||||
|
@endphp
|
||||||
|
|
||||||
<li class="nav-item dropdown-shortcuts navbar-dropdown dropdown">
|
<li class="nav-item dropdown-shortcuts navbar-dropdown dropdown">
|
||||||
<a class="nav-link btn btn-text-secondary btn-icon rounded-pill btn-icon dropdown-toggle hide-arrow" href="javascript:void(0);" data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="false">
|
<a class="nav-link btn btn-text-secondary btn-icon rounded-pill btn-icon dropdown-toggle hide-arrow" href="javascript:void(0);" data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="false">
|
||||||
<i class='ti ti-layout-grid-add ti-md'></i>
|
<i class='ti ti-layout-grid-add ti-md'></i>
|
||||||
@ -16,7 +22,7 @@
|
|||||||
<i class="ti ti-trash text-heading"></i>
|
<i class="ti ti-trash text-heading"></i>
|
||||||
</button>
|
</button>
|
||||||
@else
|
@else
|
||||||
@if($vuexyQuickLinks['totalLinks'] < config('koneko.admin.vuexy.maxQuickLinks', 8))
|
@if($vuexyQuickLinks['totalLinks'] < $maxQuickLinks)
|
||||||
<button
|
<button
|
||||||
wire:click="add('{{ Route::currentRouteName() }}')"
|
wire:click="add('{{ Route::currentRouteName() }}')"
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -63,19 +63,19 @@
|
|||||||
<!-- Título -->
|
<!-- Título -->
|
||||||
<h6 class="mb-0 text-dark dark:text-light fw-semibold search-term">
|
<h6 class="mb-0 text-dark dark:text-light fw-semibold search-term">
|
||||||
{{ $item['title'] }}
|
{{ $item['title'] }}
|
||||||
@if(config('koneko.admin.menu.debug.show_broken_routers') && $item['url'] == "javascript:;")
|
@if(CoreModule::config('menu.debug.show_broken_routers') && $item['url'] == "javascript:;")
|
||||||
<p class="text-xs m-0 pt-2 text-gray-500">
|
<p class="text-xs m-0 pt-2 text-gray-500">
|
||||||
<span class="xs mr-1">❌</span>
|
<span class="xs mr-1">❌</span>
|
||||||
Sin URL valida
|
Sin URL valida
|
||||||
</p>
|
</p>
|
||||||
@endif
|
@endif
|
||||||
@if(config('koneko.admin.menu.debug.show_disallowed_links') && $item['disallowed_link'])
|
@if(CoreModule::config('menu.debug.show_disallowed_links') && $item['disallowed_link'])
|
||||||
<p class="text-xs m-0 pt-2 text-gray-500">
|
<p class="text-xs m-0 pt-2 text-gray-500">
|
||||||
<span class="text-sm mr-1">🔒</span>
|
<span class="text-sm mr-1">🔒</span>
|
||||||
Sin permisos
|
Sin permisos
|
||||||
</p>
|
</p>
|
||||||
@endif
|
@endif
|
||||||
@if(config('koneko.admin.menu.debug.show_hidden_items') && $item['hidden_item'])
|
@if(CoreModule::config('menu.debug.show_hidden_items') && $item['hidden_item'])
|
||||||
<p class="text-xs m-0 pt-2 text-gray-500">
|
<p class="text-xs m-0 pt-2 text-gray-500">
|
||||||
<span class="text-sm mr-1">🚧</span>
|
<span class="text-sm mr-1">🚧</span>
|
||||||
Vista forzada
|
Vista forzada
|
||||||
|
@ -3,6 +3,11 @@
|
|||||||
* Vista Blade para mostrar los accesos rápidos.
|
* Vista Blade para mostrar los accesos rápidos.
|
||||||
* Compatible con Vuexy Admin y modo oscuro.
|
* Compatible con Vuexy Admin y modo oscuro.
|
||||||
*/
|
*/
|
||||||
|
$menuDebug = config_m()->get('menu.debug', []);
|
||||||
|
|
||||||
|
$show_broken_routes = $menuDebug['show_broken_routes'] ?? false;
|
||||||
|
$show_disallowed_links = $menuDebug['show_disallowed_links'] ?? false;
|
||||||
|
$show_hidden_items = $menuDebug['show_hidden_items'] ?? false;
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
@ -59,17 +64,17 @@
|
|||||||
<i class="{{ $item['icon'] }} text-3xl text-primary mt-1 mb-3"></i>
|
<i class="{{ $item['icon'] }} text-3xl text-primary mt-1 mb-3"></i>
|
||||||
<h6 class="mb-0 text-dark dark:text-light fw-semibold search-term">
|
<h6 class="mb-0 text-dark dark:text-light fw-semibold search-term">
|
||||||
{{ $item['title'] }}
|
{{ $item['title'] }}
|
||||||
@if(config('koneko.admin.menu.debug.show_broken_routers') && $item['url'] == "javascript:;")
|
@if($show_broken_routes && $item['url'] == "javascript:;")
|
||||||
<p class="text-xs m-0 pt-2 text-gray-500">
|
<p class="text-xs m-0 pt-2 text-gray-500">
|
||||||
<span class="xs mr-1">❌</span> Sin URL válida
|
<span class="xs mr-1">❌</span> Sin URL válida
|
||||||
</p>
|
</p>
|
||||||
@endif
|
@endif
|
||||||
@if(config('koneko.admin.menu.debug.show_disallowed_links') && $item['disallowed_link'])
|
@if($show_disallowed_links && $item['disallowed_link'])
|
||||||
<p class="text-xs m-0 pt-2 text-gray-500">
|
<p class="text-xs m-0 pt-2 text-gray-500">
|
||||||
<span class="text-sm mr-1">🔒</span> Sin permisos
|
<span class="text-sm mr-1">🔒</span> Sin permisos
|
||||||
</p>
|
</p>
|
||||||
@endif
|
@endif
|
||||||
@if(config('koneko.admin.menu.debug.show_hidden_items') && $item['hidden_item'])
|
@if($show_hidden_items && $item['hidden_item'])
|
||||||
<p class="text-xs m-0 pt-2 text-gray-500">
|
<p class="text-xs m-0 pt-2 text-gray-500">
|
||||||
<span class="text-sm mr-1">🚧</span> Vista forzada
|
<span class="text-sm mr-1">🚧</span> Vista forzada
|
||||||
</p>
|
</p>
|
||||||
|
@ -5,7 +5,6 @@ use Koneko\VuexyAdmin\Application\Http\Controllers\HomeController;
|
|||||||
use Koneko\VuexyAdmin\Application\UX\Menu\VuexyMenuFormatter;
|
use Koneko\VuexyAdmin\Application\UX\Menu\VuexyMenuFormatter;
|
||||||
use Koneko\VuexyAdmin\Support\Routing\RouteScope;
|
use Koneko\VuexyAdmin\Support\Routing\RouteScope;
|
||||||
|
|
||||||
|
|
||||||
RouteScope::auto(__FILE__, function (RouteScope $r) {
|
RouteScope::auto(__FILE__, function (RouteScope $r) {
|
||||||
$r->route('', 'pages.', HomeController::class, function () {
|
$r->route('', 'pages.', HomeController::class, function () {
|
||||||
Route::get('acerca-de', 'about')->name('about.index');
|
Route::get('acerca-de', 'about')->name('about.index');
|
||||||
@ -35,5 +34,4 @@ RouteScope::auto(__FILE__, function (RouteScope $r) {
|
|||||||
]);
|
]);
|
||||||
})->name('folder.view');
|
})->name('folder.view');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -9,8 +9,8 @@ use Illuminate\Support\Facades\Log;
|
|||||||
use Koneko\VuexyAdmin\Application\Bootstrap\KonekoModule;
|
use Koneko\VuexyAdmin\Application\Bootstrap\KonekoModule;
|
||||||
use Koneko\VuexyApisAndIntegrations\Models\ExternalApi;
|
use Koneko\VuexyApisAndIntegrations\Models\ExternalApi;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Koneko\VuexyAdmin\Application\Bootstrap\KonekoModuleRegistry;
|
use Koneko\VuexyAdmin\Application\Bootstrap\Registry\KonekoModuleRegistry;
|
||||||
use Koneko\VuexyAdmin\Application\Bootstrap\KonekoComponentContextRegistrar;
|
use Koneko\VuexyAdmin\Application\Bootstrap\Registry\KonekoComponentContextRegistrar;
|
||||||
|
|
||||||
class ApiModuleRegistry
|
class ApiModuleRegistry
|
||||||
{
|
{
|
||||||
|
@ -5,7 +5,11 @@ declare(strict_types=1);
|
|||||||
namespace Koneko\VuexyAdmin\Application\Bootstrap;
|
namespace Koneko\VuexyAdmin\Application\Bootstrap;
|
||||||
|
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use Koneko\VuexyAdmin\Application\Bootstrap\Registry\KonekoModuleRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clase representativa de un módulo del sistema Vuexy Admin
|
||||||
|
*/
|
||||||
class KonekoModule
|
class KonekoModule
|
||||||
{
|
{
|
||||||
// === Identidad del Módulo ===
|
// === Identidad del Módulo ===
|
||||||
@ -23,10 +27,10 @@ class KonekoModule
|
|||||||
public string $minimumStability;
|
public string $minimumStability;
|
||||||
public string $buildVersion;
|
public string $buildVersion;
|
||||||
|
|
||||||
// === Namespace de configuraciones ===
|
// === Namespace del componente para el sistema de configuración ===
|
||||||
public string $componentNamespace;
|
public string $componentNamespace;
|
||||||
|
|
||||||
// === Datos de composer/autoload ===
|
// === Composer & Autoload ===
|
||||||
public string $composerName;
|
public string $composerName;
|
||||||
public string $namespace;
|
public string $namespace;
|
||||||
public ?string $provider;
|
public ?string $provider;
|
||||||
@ -34,126 +38,50 @@ class KonekoModule
|
|||||||
public string $composerPath;
|
public string $composerPath;
|
||||||
public array $dependencies;
|
public array $dependencies;
|
||||||
|
|
||||||
// === Metadatos visuales para UI del gestor ===
|
// === Metadatos visuales UI ===
|
||||||
public array $ui;
|
public array $ui;
|
||||||
|
|
||||||
// === Archivos de configuraciones ===
|
// === Definiciones técnicas del módulo ===
|
||||||
public array $configs;
|
public array $configs;
|
||||||
|
|
||||||
// === Providers, Middleware y Aliases (runtime) ===
|
|
||||||
public array $providers;
|
public array $providers;
|
||||||
public array $middleware;
|
public array $middleware;
|
||||||
public array $aliases;
|
public array $aliases;
|
||||||
|
|
||||||
// === Singleton *Nuevo Prototipo ===
|
|
||||||
public array $singletons;
|
public array $singletons;
|
||||||
|
|
||||||
// === Bindings de interfaces a servicios ===
|
|
||||||
public array $bindings;
|
public array $bindings;
|
||||||
|
|
||||||
// === Macro ===
|
|
||||||
public array $macros;
|
public array $macros;
|
||||||
|
|
||||||
// === Observadores y Auditable ===
|
|
||||||
public array $observers;
|
public array $observers;
|
||||||
public array $listeners;
|
public array $listeners;
|
||||||
public array $auditable;
|
public array $auditable;
|
||||||
|
|
||||||
// === Migraciones ===
|
|
||||||
public array $migrations;
|
public array $migrations;
|
||||||
|
|
||||||
// === Rutas ===
|
|
||||||
public array $routes;
|
public array $routes;
|
||||||
|
|
||||||
// === Vistas y traducciones ===
|
|
||||||
public array $views;
|
public array $views;
|
||||||
public array $translations;
|
public array $translations;
|
||||||
|
|
||||||
// === Livewire, Blade, Vistas ===
|
|
||||||
public array $bladeComponents;
|
public array $bladeComponents;
|
||||||
public array $livewire;
|
public array $livewire;
|
||||||
|
|
||||||
// === Archivos publicables ===
|
|
||||||
public array $publishedFiles;
|
public array $publishedFiles;
|
||||||
|
|
||||||
// === Comandos Artisan ===
|
|
||||||
public array $commands;
|
public array $commands;
|
||||||
|
|
||||||
public array $schedules;
|
public array $schedules;
|
||||||
|
|
||||||
// === Roles y permisos ===
|
|
||||||
public array $rbac;
|
public array $rbac;
|
||||||
|
|
||||||
// === APIs ===
|
|
||||||
public array $apis;
|
public array $apis;
|
||||||
|
|
||||||
// === Catálogos ===
|
|
||||||
public array $catalogs;
|
public array $catalogs;
|
||||||
|
|
||||||
// === Extensiones del ecosistema ===
|
|
||||||
public array $extensions;
|
public array $extensions;
|
||||||
|
public array $scopeModels;
|
||||||
|
public array $configBlocks;
|
||||||
|
|
||||||
public function __construct(array $overrides = [])
|
public function __construct(array $overrides = [])
|
||||||
{
|
{
|
||||||
$this->vendor = $overrides['vendor'] ?? null;
|
foreach ((new \ReflectionClass($this))->getProperties() as $property) {
|
||||||
$this->name = $overrides['name'] ?? '';
|
$name = $property->getName();
|
||||||
$this->slug = $overrides['slug'] ?? '';
|
$this->$name = $overrides[$name] ?? $this->$name ?? ($property->hasType() && $property->getType()->getName() === 'array' ? [] : null);
|
||||||
$this->description = $overrides['description'] ?? '';
|
}
|
||||||
$this->type = $overrides['type'] ?? 'plugin';
|
|
||||||
$this->tags = $overrides['tags'] ?? [];
|
|
||||||
$this->version = $overrides['version'] ?? '1.0.0';
|
|
||||||
$this->keywords = $overrides['keywords'] ?? [];
|
|
||||||
$this->authors = $overrides['authors'] ?? [];
|
|
||||||
$this->support = $overrides['support'] ?? [];
|
|
||||||
$this->license = $overrides['license'] ?? null;
|
|
||||||
$this->minimumStability= $overrides['minimumStability']?? 'stable';
|
|
||||||
$this->buildVersion = $overrides['buildVersion'] ?? now()->format('YmdHis');
|
|
||||||
|
|
||||||
$this->componentNamespace = $overrides['componentNamespace'] ?? '';
|
// Defaults no cubiertos
|
||||||
|
$this->type ??= 'plugin';
|
||||||
$this->composerName = $overrides['composerName'] ?? '';
|
$this->version ??= '1.0.0';
|
||||||
$this->namespace = $overrides['namespace'] ?? '';
|
$this->minimumStability??= 'stable';
|
||||||
$this->provider = $overrides['provider'] ?? null;
|
$this->buildVersion ??= now()->format('YmdHis');
|
||||||
$this->basePath = $overrides['basePath'] ?? '';
|
$this->description ??= '';
|
||||||
$this->composerPath = $overrides['composerPath'] ?? '';
|
$this->componentNamespace ??= $overrides['componentNamespace'] ?? '';
|
||||||
$this->dependencies = $overrides['dependencies'] ?? [];
|
|
||||||
|
|
||||||
$this->ui = $overrides['ui'] ?? [];
|
|
||||||
|
|
||||||
$this->configs = $overrides['configs'] ?? [];
|
|
||||||
|
|
||||||
$this->providers = $overrides['providers'] ?? [];
|
|
||||||
$this->middleware = $overrides['middleware'] ?? [];
|
|
||||||
$this->aliases = $overrides['aliases'] ?? [];
|
|
||||||
|
|
||||||
$this->singletons = $overrides['singletons'] ?? [];
|
|
||||||
$this->bindings = $overrides['bindings'] ?? [];
|
|
||||||
$this->macros = $overrides['macros'] ?? [];
|
|
||||||
|
|
||||||
$this->observers = $overrides['observers'] ?? [];
|
|
||||||
$this->listeners = $overrides['listeners'] ?? [];
|
|
||||||
$this->auditable = $overrides['auditable'] ?? [];
|
|
||||||
|
|
||||||
$this->migrations = $overrides['migrations'] ?? [];
|
|
||||||
|
|
||||||
$this->routes = $overrides['routes'] ?? [];
|
|
||||||
|
|
||||||
$this->views = $overrides['views'] ?? [];
|
|
||||||
$this->translations = $overrides['translations'] ?? [];
|
|
||||||
|
|
||||||
$this->bladeComponents = $overrides['bladeComponents'] ?? [];
|
|
||||||
$this->livewire = $overrides['livewire'] ?? [];
|
|
||||||
|
|
||||||
$this->publishedFiles = $overrides['publishedFiles'] ?? [];
|
|
||||||
|
|
||||||
$this->commands = $overrides['commands'] ?? [];
|
|
||||||
$this->schedules = $overrides['schedules'] ?? [];
|
|
||||||
|
|
||||||
$this->rbac = $overrides['rbac'] ?? [];
|
|
||||||
$this->apis = $overrides['apis'] ?? [];
|
|
||||||
$this->catalogs = $overrides['catalogs'] ?? [];
|
|
||||||
|
|
||||||
$this->extensions = $overrides['extensions'] ?? [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): string
|
public function getId(): string
|
||||||
@ -208,9 +136,9 @@ class KonekoModule
|
|||||||
'version' => $this->version,
|
'version' => $this->version,
|
||||||
'buildVersion' => $this->buildVersion,
|
'buildVersion' => $this->buildVersion,
|
||||||
'type' => $this->type,
|
'type' => $this->type,
|
||||||
'vendor' => $this->vendor ?? null,
|
'vendor' => $this->vendor,
|
||||||
'license' => $this->license ?? 'proprietary',
|
'license' => $this->license ?? 'proprietary',
|
||||||
'minimumStability' => $this->minimumStability ?? 'stable',
|
'minimumStability' => $this->minimumStability,
|
||||||
'tags' => $this->tags,
|
'tags' => $this->tags,
|
||||||
'authors' => $this->authors,
|
'authors' => $this->authors,
|
||||||
'support' => $this->support,
|
'support' => $this->support,
|
||||||
@ -221,92 +149,42 @@ class KonekoModule
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function buildAttributesFromComposer(array $composer, string $basePath, array $custom = []): array
|
public static function fromComposerJson(array $composer, string $basePath, array $custom = []): self
|
||||||
{
|
{
|
||||||
$nameParts = explode('/', $composer['name'] ?? 'unknown/module');
|
return new self(
|
||||||
$vendor = $nameParts[0] ?? 'unknown';
|
static::buildAttributesFromComposer($composer, $basePath, $custom)
|
||||||
$slug = $nameParts[1] ?? 'module';
|
);
|
||||||
|
|
||||||
return [
|
|
||||||
'vendor' => $vendor,
|
|
||||||
'slug' => $slug,
|
|
||||||
'name' => $custom['name'] ?? 'unknown/module',
|
|
||||||
'description' => $custom['description'] ?? ($composer['description'] ?? ''),
|
|
||||||
'type' => $custom['type'] ?? ($composer['type'] ?? 'plugin'),
|
|
||||||
'tags' => $custom['tags'] ?? ($composer['keywords'] ?? []),
|
|
||||||
'composerName' => $composer['name'] ?? 'unknown/module',
|
|
||||||
'version' => $composer['version'] ?? '1.0.0',
|
|
||||||
'keywords' => $composer['keywords'] ?? [],
|
|
||||||
'authors' => $composer['authors'] ?? [],
|
|
||||||
'support' => $composer['support'] ?? [],
|
|
||||||
'license' => $composer['license'] ?? 'proprietary',
|
|
||||||
'minimumStability' => $composer['minimum-stability'] ?? 'stable',
|
|
||||||
'buildVersion' => now()->format('YmdHis'), // Versión de compilación en timestamp
|
|
||||||
'namespace' => array_key_first($composer['autoload']['psr-4'] ?? []) ?? '',
|
|
||||||
'provider' => $composer['extra']['laravel']['providers'][0] ?? null,
|
|
||||||
'basePath' => rtrim($basePath, '/'),
|
|
||||||
'composerPath' => rtrim($basePath, '/') . '/composer.json',
|
|
||||||
'dependencies' => array_keys($composer['require'] ?? []),
|
|
||||||
'ui' => $custom['ui'] ?? [],
|
|
||||||
'configs' => $custom['configs'] ?? [],
|
|
||||||
'componentNamespace' => $custom['componentNamespace'] ?? '',
|
|
||||||
'providers' => $custom['providers'] ?? [],
|
|
||||||
'middleware' => $custom['middleware'] ?? [],
|
|
||||||
'aliases' => $custom['aliases'] ?? [],
|
|
||||||
'singletons' => $custom['singletons'] ?? [],
|
|
||||||
'bindings' => $custom['bindings'] ?? [],
|
|
||||||
'macros' => $custom['macros'] ?? [],
|
|
||||||
'observers' => $custom['observers'] ?? [],
|
|
||||||
'listeners' => $custom['listeners'] ?? [],
|
|
||||||
'auditable' => $custom['auditable'] ?? [],
|
|
||||||
'migrations' => $custom['migrations'] ?? [],
|
|
||||||
'routes' => $custom['routes'] ?? [],
|
|
||||||
'views' => $custom['views'] ?? [],
|
|
||||||
'translations' => $custom['translations'] ?? [],
|
|
||||||
'bladeComponents' => $custom['bladeComponents'] ?? [],
|
|
||||||
'livewire' => $custom['livewire'] ?? [],
|
|
||||||
'publishedFiles' => $custom['publishedFiles'] ?? [],
|
|
||||||
'commands' => $custom['commands'] ?? [],
|
|
||||||
'schedules' => $custom['schedules'] ?? [],
|
|
||||||
'rbac' => $custom['rbac'] ?? [],
|
|
||||||
'apis' => $custom['apis'] ?? [],
|
|
||||||
'catalogs' => $custom['catalogs'] ?? [],
|
|
||||||
'extensions' => $custom['extensions'] ?? [],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function fromModuleDirectory(string $dirPath): ?KonekoModule
|
public static function fromModuleFile(string $file): ?self
|
||||||
{
|
{
|
||||||
$file = null;
|
if (!file_exists($file)) return null;
|
||||||
|
|
||||||
if (file_exists($dirPath . '/vuexy-admin.module.php')){
|
|
||||||
$file = $dirPath . '/vuexy-admin.module.php';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file_exists($dirPath . '/koneko-vuexy.module.php')){
|
|
||||||
$file = $dirPath . '/koneko-vuexy.module.php';
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = require $file;
|
$result = require $file;
|
||||||
|
|
||||||
if ($result instanceof KonekoModule) {
|
if ($result instanceof self) return $result;
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_array($result)) {
|
if (is_array($result)) {
|
||||||
// Inferir basePath desde el archivo
|
return self::fromModuleDefinition(dirname($file, 2), $result);
|
||||||
$basePath = dirname($file, 2);
|
|
||||||
|
|
||||||
return self::fromModuleDefinition($basePath, $result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new \RuntimeException("El archivo [$file] no contiene una instancia ni un array válido para definir el módulo.");
|
throw new \RuntimeException("El archivo [$file] no contiene una instancia ni un array válido para definir el módulo.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function fromModuleDirectory(string $dirPath): ?self
|
||||||
|
{
|
||||||
|
foreach (['vuexy-admin.module.php', 'koneko-vuexy.module.php'] as $fileName) {
|
||||||
|
$file = $dirPath . '/' . $fileName;
|
||||||
|
if (file_exists($file)) {
|
||||||
|
return self::fromModuleFile($file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public static function fromModuleDefinition(string $basePath, array $custom = []): self
|
public static function fromModuleDefinition(string $basePath, array $custom = []): self
|
||||||
{
|
{
|
||||||
$composerPath = rtrim($basePath, '/') . '/composer.json';
|
$composerPath = rtrim($basePath, '/') . '/composer.json';
|
||||||
|
|
||||||
if (!file_exists($composerPath)) {
|
if (!file_exists($composerPath)) {
|
||||||
throw new \RuntimeException("No se encontró composer.json en: {$composerPath}");
|
throw new \RuntimeException("No se encontró composer.json en: {$composerPath}");
|
||||||
}
|
}
|
||||||
@ -317,78 +195,48 @@ class KonekoModule
|
|||||||
throw new \RuntimeException("composer.json inválido en: {$composerPath}");
|
throw new \RuntimeException("composer.json inválido en: {$composerPath}");
|
||||||
}
|
}
|
||||||
|
|
||||||
$attributes = static::buildAttributesFromComposer($composer, realpath($basePath), $custom);
|
|
||||||
|
|
||||||
$module = new self($attributes);
|
|
||||||
|
|
||||||
return $module;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static function fromComposerJson(array $composer, string $basePath, array $custom = []): self
|
|
||||||
{
|
|
||||||
return new self(
|
return new self(
|
||||||
static::buildAttributesFromComposer($composer, $basePath, $custom)
|
static::buildAttributesFromComposer($composer, realpath($basePath), $custom)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static function buildAttributesFromComposer(array $composer, string $basePath, array $custom = []): array
|
||||||
* Registra desde archivo PHP que retorna un KonekoModule.
|
|
||||||
*/
|
|
||||||
public static function fromModuleFile(string $file): ?KonekoModule
|
|
||||||
{
|
{
|
||||||
if (!file_exists($file)) return null;
|
$nameParts = explode('/', $composer['name'] ?? 'unknown/module');
|
||||||
|
$vendor = $nameParts[0] ?? 'unknown';
|
||||||
|
$slug = $nameParts[1] ?? 'module';
|
||||||
|
|
||||||
$result = require $file;
|
return array_merge([
|
||||||
|
'vendor' => $vendor,
|
||||||
if ($result instanceof KonekoModule) {
|
'slug' => $slug,
|
||||||
return $result;
|
'name' => $custom['name'] ?? $composer['name'] ?? 'unknown/module',
|
||||||
}
|
'description' => $composer['description'] ?? '',
|
||||||
|
'type' => $composer['type'] ?? 'plugin',
|
||||||
if (is_array($result)) {
|
'tags' => $composer['keywords'] ?? [],
|
||||||
// Inferir basePath desde el archivo
|
'composerName' => $composer['name'] ?? 'unknown/module',
|
||||||
$basePath = dirname($file, 2);
|
'version' => $composer['version'] ?? '1.0.0',
|
||||||
|
'keywords' => $composer['keywords'] ?? [],
|
||||||
return self::fromModuleDefinition($basePath, $result);
|
'authors' => $composer['authors'] ?? [],
|
||||||
}
|
'support' => $composer['support'] ?? [],
|
||||||
|
'license' => $composer['license'] ?? 'proprietary',
|
||||||
throw new \RuntimeException("El archivo [$file] no contiene una instancia ni un array válido para definir el módulo.");
|
'minimumStability' => $composer['minimum-stability'] ?? 'stable',
|
||||||
|
'buildVersion' => now()->format('YmdHis'),
|
||||||
|
'namespace' => array_key_first($composer['autoload']['psr-4'] ?? []) ?? '',
|
||||||
|
'provider' => $composer['extra']['laravel']['providers'][0] ?? null,
|
||||||
|
'basePath' => rtrim($basePath, '/'),
|
||||||
|
'composerPath' => rtrim($basePath, '/') . '/composer.json',
|
||||||
|
'dependencies' => array_keys($composer['require'] ?? []),
|
||||||
|
], $custom);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtiene el slug del módulo actualmente activo, basado en la ruta.
|
|
||||||
*/
|
|
||||||
public static function currentSlug(): string
|
public static function currentSlug(): string
|
||||||
{
|
{
|
||||||
$module = static::resolveCurrent();
|
return static::resolveCurrent()?->slug ?? 'unknown';
|
||||||
|
|
||||||
return $module?->slug ?? 'unknown';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtiene el módulo actual, basado en la ruta.
|
|
||||||
*
|
|
||||||
* @return static|null
|
|
||||||
*/
|
|
||||||
public static function resolveCurrent(): ?self
|
public static function resolveCurrent(): ?self
|
||||||
{
|
{
|
||||||
$modules = KonekoModuleRegistry::enabled();
|
$modules = KonekoModuleRegistry::enabled();
|
||||||
|
|
||||||
if (empty($modules)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$currentRoute = request()?->route()?->getName();
|
$currentRoute = request()?->route()?->getName();
|
||||||
|
|
||||||
foreach ($modules as $module) {
|
foreach ($modules as $module) {
|
||||||
@ -397,7 +245,6 @@ class KonekoModule
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: el primero que esté activo
|
|
||||||
return reset($modules) ?: null;
|
return reset($modules) ?: null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,19 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Bootstrap;
|
namespace Koneko\VuexyAdmin\Application\Bootstrap\Manager;
|
||||||
|
|
||||||
use Illuminate\Console\Scheduling\Schedule;
|
use Illuminate\Console\Scheduling\Schedule;
|
||||||
use Illuminate\Foundation\AliasLoader;
|
use Illuminate\Foundation\AliasLoader;
|
||||||
use Illuminate\Routing\Router;
|
use Illuminate\Routing\Router;
|
||||||
use Illuminate\Support\Facades\{App,Blade,Event,Lang,Route,View};
|
use Illuminate\Support\Facades\{App, Blade, Event, Lang, Route, View};
|
||||||
use Koneko\VuexyAdmin\Application\Bootstrap\Extenders\Catalog\CatalogModuleRegistry;
|
use Koneko\VuexyAdmin\Application\Bootstrap\Extenders\Catalog\CatalogModuleRegistry;
|
||||||
use Koneko\VuexyAdmin\Application\Factories\FactoryExtensionRegistry;
|
use Koneko\VuexyAdmin\Application\Factories\FactoryExtensionRegistry;
|
||||||
use Koneko\VuexyAdmin\Application\Bootstrap\Extenders\Model\ModelExtensionRegistry;
|
use Koneko\VuexyAdmin\Application\Bootstrap\Extenders\Model\ModelExtensionRegistry;
|
||||||
|
use Koneko\VuexyAdmin\Application\Bootstrap\KonekoModule;
|
||||||
|
use Koneko\VuexyAdmin\Application\Bootstrap\Registry\KonekoModuleRegistry;
|
||||||
|
use Koneko\VuexyAdmin\Application\Config\Registry\ConfigBlockRegistry;
|
||||||
|
use Koneko\VuexyAdmin\Application\Settings\Registry\ScopeRegistry;
|
||||||
use Livewire\Livewire;
|
use Livewire\Livewire;
|
||||||
|
|
||||||
class KonekoModuleBootManager
|
class KonekoModuleBootManager
|
||||||
@ -27,58 +31,56 @@ class KonekoModuleBootManager
|
|||||||
|
|
||||||
public static function boot(KonekoModule $module): void
|
public static function boot(KonekoModule $module): void
|
||||||
{
|
{
|
||||||
// ⚙️ Archivos de configuración del módulo
|
// =========================================
|
||||||
|
// 1️⃣ CONFIGURACIONES DEL MÓDULO
|
||||||
|
// =========================================
|
||||||
foreach ($module->configs ?? [] as $namespace => $relativePath) {
|
foreach ($module->configs ?? [] as $namespace => $relativePath) {
|
||||||
$fullPath = $module->basePath . DIRECTORY_SEPARATOR . $relativePath;
|
$filename = basename($relativePath);
|
||||||
|
$projectPath = config_path($filename);
|
||||||
|
$modulePath = $module->basePath . DIRECTORY_SEPARATOR . $relativePath;
|
||||||
|
|
||||||
if (file_exists($fullPath)) {
|
$config = null;
|
||||||
$config = require $fullPath;
|
|
||||||
|
|
||||||
if (is_array($config)) {
|
if (file_exists($projectPath)) {
|
||||||
config()->set($namespace, array_merge(
|
$config = require $projectPath;
|
||||||
config($namespace, []),
|
|
||||||
$config
|
} elseif (file_exists($modulePath)) {
|
||||||
));
|
$config = require $modulePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_array($config)) {
|
||||||
|
config()->set($namespace, $config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🛡️ Middleware
|
// =========================================
|
||||||
|
// 2️⃣ INFRAESTRUCTURA BASE (App Bindings)
|
||||||
|
// =========================================
|
||||||
foreach ($module->middleware ?? [] as $alias => $middlewareClass) {
|
foreach ($module->middleware ?? [] as $alias => $middlewareClass) {
|
||||||
// @var Router $router
|
app(Router::class)->aliasMiddleware($alias, $middlewareClass);
|
||||||
$router = app(Router::class);
|
|
||||||
$router->aliasMiddleware($alias, $middlewareClass);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🏭 Proveedores de servicio
|
|
||||||
foreach ($module->providers ?? [] as $provider) {
|
foreach ($module->providers ?? [] as $provider) {
|
||||||
App::register($provider);
|
App::register($provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🧩 Alias de clases
|
|
||||||
foreach ($module->aliases ?? [] as $alias => $class) {
|
foreach ($module->aliases ?? [] as $alias => $class) {
|
||||||
AliasLoader::getInstance()->alias($alias, $class);
|
AliasLoader::getInstance()->alias($alias, $class);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔩 Singletons
|
foreach ($module->Singletons ?? [] as $singleton) {
|
||||||
foreach ($model->Singletons?? [] as $singletone) {
|
app()->singleton($singleton);
|
||||||
app()->singleton($singletone);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔗 Bindings de interfaces a servicios
|
|
||||||
foreach ($module->bindings ?? [] as $abstract => $concrete) {
|
foreach ($module->bindings ?? [] as $abstract => $concrete) {
|
||||||
app()->singleton($abstract, $concrete);
|
app()->singleton($abstract, $concrete);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ⚙️ Namespace de componentes
|
// =========================================
|
||||||
if (!empty($module->componentNamespace)) {
|
// 3️⃣ FUNCIONALIDADES COMPLEMENTARIAS
|
||||||
KonekoComponentContextRegistrar::registerComponent($module->componentNamespace);
|
// =========================================
|
||||||
}
|
|
||||||
|
|
||||||
// 📜 Macros
|
|
||||||
foreach ($module->macros ?? [] as $macroPath) {
|
foreach ($module->macros ?? [] as $macroPath) {
|
||||||
$fullPath = static::resolvePath($module, $macroPath);
|
$fullPath = static::resolvePath($module, $macroPath);
|
||||||
|
|
||||||
if (file_exists($fullPath)) {
|
if (file_exists($fullPath)) {
|
||||||
require_once $fullPath;
|
require_once $fullPath;
|
||||||
|
|
||||||
@ -87,12 +89,10 @@ class KonekoModuleBootManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔊 Eventos
|
|
||||||
foreach ($module->listeners ?? [] as $event => $listener) {
|
foreach ($module->listeners ?? [] as $event => $listener) {
|
||||||
Event::listen($event, $listener);
|
Event::listen($event, $listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔍 Observadores
|
|
||||||
foreach ($module->observers ?? [] as $model => $observers) {
|
foreach ($module->observers ?? [] as $model => $observers) {
|
||||||
if (!class_exists($model)) {
|
if (!class_exists($model)) {
|
||||||
logger()->warning("🔍 Modelo no encontrado para observers: $model");
|
logger()->warning("🔍 Modelo no encontrado para observers: $model");
|
||||||
@ -102,30 +102,29 @@ class KonekoModuleBootManager
|
|||||||
foreach ((array) $observers as $observer) {
|
foreach ((array) $observers as $observer) {
|
||||||
if (class_exists($observer)) {
|
if (class_exists($observer)) {
|
||||||
$model::observe($observer);
|
$model::observe($observer);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
logger()->warning("⚠️ Observer no válido: {$observer}");
|
logger()->warning("⚠️ Observer no válido: {$observer}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 🧪 Modelos auditables
|
|
||||||
foreach ($module->auditable ?? [] as $model) {
|
foreach ($module->auditable ?? [] as $model) {
|
||||||
if (class_exists($model)) {
|
if (class_exists($model)) {
|
||||||
$model::observe(\OwenIt\Auditing\AuditableObserver::class);
|
$model::observe(\OwenIt\Auditing\AuditableObserver::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🧬 Migraciones
|
// =========================================
|
||||||
|
// 4️⃣ DEFINICIONES TÉCNICAS Y RUTAS
|
||||||
|
// =========================================
|
||||||
foreach ($module->migrations ?? [] as $relativePath) {
|
foreach ($module->migrations ?? [] as $relativePath) {
|
||||||
$fullPath = static::resolvePath($module, $relativePath);
|
$fullPath = static::resolvePath($module, $relativePath);
|
||||||
|
|
||||||
if (is_dir($fullPath)) {
|
if (is_dir($fullPath)) {
|
||||||
app()->make('migrator')->path($fullPath);
|
app()->make('migrator')->path($fullPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🗺️ Rutas (después de setModule)
|
|
||||||
foreach ($module->routes ?? [] as $routeGroup) {
|
foreach ($module->routes ?? [] as $routeGroup) {
|
||||||
$middleware = $routeGroup['middleware'] ?? [];
|
$middleware = $routeGroup['middleware'] ?? [];
|
||||||
$paths = $routeGroup['paths'] ?? [];
|
$paths = $routeGroup['paths'] ?? [];
|
||||||
@ -141,65 +140,80 @@ class KonekoModuleBootManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🗂️ Vistas
|
// =========================================
|
||||||
|
// 5️⃣ RECURSOS: VISTAS, TRADUCCIONES Y COMPONENTES
|
||||||
|
// =========================================
|
||||||
foreach ($module->views ?? [] as $namespace => $path) {
|
foreach ($module->views ?? [] as $namespace => $path) {
|
||||||
View::addNamespace($namespace, static::resolvePath($module, $path));
|
View::addNamespace($namespace, static::resolvePath($module, $path));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🌍 Traducciones
|
|
||||||
foreach ($module->translations ?? [] as $namespace => $path) {
|
foreach ($module->translations ?? [] as $namespace => $path) {
|
||||||
Lang::addNamespace($namespace, static::resolvePath($module, $path));
|
Lang::addNamespace($namespace, static::resolvePath($module, $path));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🧩 Componentes Blade
|
|
||||||
foreach ($module->bladeComponents ?? [] as $prefix => $namespace) {
|
foreach ($module->bladeComponents ?? [] as $prefix => $namespace) {
|
||||||
Blade::componentNamespace($namespace, $prefix);
|
Blade::componentNamespace($namespace, $prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ⚡ Livewire
|
|
||||||
foreach ($module->livewire ?? [] as $namespace => $components) {
|
foreach ($module->livewire ?? [] as $namespace => $components) {
|
||||||
foreach ($components as $alias => $class) {
|
foreach ($components as $alias => $class) {
|
||||||
Livewire::component("{$namespace}::{$alias}", $class);
|
Livewire::component("{$namespace}::{$alias}", $class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🛠 Comandos Artisan
|
// =========================================
|
||||||
|
// 6️⃣ CONSOLA Y DEFINICIONES EXTERNAS
|
||||||
|
// =========================================
|
||||||
if (App::runningInConsole() && !empty($module->commands)) {
|
if (App::runningInConsole() && !empty($module->commands)) {
|
||||||
static::registerCommands($module->commands);
|
static::registerCommands($module->commands);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔗 Registro de APIs disponibles en el módulo
|
|
||||||
if (!empty($module->apis['catalog_path'])) {
|
if (!empty($module->apis['catalog_path'])) {
|
||||||
$apiCatalogPath = static::resolvePath($module, $module->apis['catalog_path']);
|
$apiCatalogPath = static::resolvePath($module, $module->apis['catalog_path']);
|
||||||
|
|
||||||
if (file_exists($apiCatalogPath)) {
|
if (file_exists($apiCatalogPath)) {
|
||||||
$apiDefinitions = require $apiCatalogPath;
|
$apiDefinitions = require $apiCatalogPath;
|
||||||
|
|
||||||
config()->set("apis.{$module->slug}", $apiDefinitions);
|
config()->set("apis.{$module->slug}", $apiDefinitions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 📑 Registro de catálogos
|
// =========================================
|
||||||
|
// 7️⃣ REGISTROS INTERNOS
|
||||||
|
// =========================================
|
||||||
foreach ($module->catalogs ?? [] as $catalog => $service) {
|
foreach ($module->catalogs ?? [] as $catalog => $service) {
|
||||||
CatalogModuleRegistry::register($catalog, $service);
|
CatalogModuleRegistry::register($catalog, $service);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🧠 Extensiones de Modelos
|
|
||||||
foreach ($module->extensions['config'] ?? [] as $builder => $extensions) {
|
foreach ($module->extensions['config'] ?? [] as $builder => $extensions) {
|
||||||
ModelExtensionRegistry::registerConfigExtensions($builder, $extensions);
|
ModelExtensionRegistry::registerConfigExtensions($builder, $extensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🧪 Extensiones de Flags de Modelos
|
|
||||||
foreach ($module->extensions['model_flags'] ?? [] as $model => $flags) {
|
foreach ($module->extensions['model_flags'] ?? [] as $model => $flags) {
|
||||||
ModelExtensionRegistry::registerModelFlags($model, $flags);
|
ModelExtensionRegistry::registerModelFlags($model, $flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🧬 Extensiones de Traits de Factory
|
|
||||||
foreach ($module->extensions['factory_traits'] ?? [] as $factory => $traits) {
|
foreach ($module->extensions['factory_traits'] ?? [] as $factory => $traits) {
|
||||||
foreach ((array) $traits as $trait) {
|
foreach ((array) $traits as $trait) {
|
||||||
FactoryExtensionRegistry::registerFactoryTrait($factory, $trait);
|
FactoryExtensionRegistry::registerFactoryTrait($factory, $trait);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($module->scopeModels ?? [] as $scope => $modelClass) {
|
||||||
|
try {
|
||||||
|
ScopeRegistry::register($scope, $modelClass);
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
logger()->error("[ScopeRegistry] No se pudo registrar el scope '{$scope}' del módulo '{$module->slug}': {$e->getMessage()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($module->configBlocks ?? [] as $configKey => $blockDefinition) {
|
||||||
|
try {
|
||||||
|
ConfigBlockRegistry::register($configKey, $blockDefinition);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
logger()->warning("[ConfigBlockRegistry] No se pudo registrar el bloque '$configKey': {$e->getMessage()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -250,6 +264,7 @@ class KonekoModuleBootManager
|
|||||||
// Intenta aplicar el método al evento, no al schedule
|
// Intenta aplicar el método al evento, no al schedule
|
||||||
if ($method && method_exists($event, $method)) {
|
if ($method && method_exists($event, $method)) {
|
||||||
$event = $event->{$method}(...$params);
|
$event = $event->{$method}(...$params);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
logger()->warning("[VuexySchedule] ❌ Método inválido para evento: {$method}");
|
logger()->warning("[VuexySchedule] ❌ Método inválido para evento: {$method}");
|
||||||
continue;
|
continue;
|
||||||
@ -259,6 +274,7 @@ class KonekoModuleBootManager
|
|||||||
foreach ($chain as $chainMethod) {
|
foreach ($chain as $chainMethod) {
|
||||||
if (method_exists($event, $chainMethod)) {
|
if (method_exists($event, $chainMethod)) {
|
||||||
$event = $event->{$chainMethod}();
|
$event = $event->{$chainMethod}();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
logger()->warning("[VuexySchedule] ⚠️ Método de chain no válido: {$chainMethod} en {$jobClass}");
|
logger()->warning("[VuexySchedule] ⚠️ Método de chain no válido: {$chainMethod} en {$jobClass}");
|
||||||
}
|
}
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Bootstrap;
|
namespace Koneko\VuexyAdmin\Application\Bootstrap\Registry;
|
||||||
|
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Koneko\VuexyAdmin\Application\Bootstrap\KonekoModule;
|
use Koneko\VuexyAdmin\Application\Bootstrap\KonekoModule;
|
||||||
@ -11,6 +11,7 @@ class KonekoModuleRegistry
|
|||||||
{
|
{
|
||||||
/** @var KonekoModule[] */
|
/** @var KonekoModule[] */
|
||||||
protected static array $modules = [];
|
protected static array $modules = [];
|
||||||
|
protected static ?string $currentComponent = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registra un módulo completo.
|
* Registra un módulo completo.
|
||||||
@ -130,6 +131,18 @@ class KonekoModuleRegistry
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function setCurrent(string $component): void
|
||||||
|
{
|
||||||
|
static::$currentComponent = $component;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function current(): ?KonekoModule
|
||||||
|
{
|
||||||
|
return static::$currentComponent
|
||||||
|
? static::get(static::$currentComponent)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mapa resumido para tarjetas en UI.
|
* Mapa resumido para tarjetas en UI.
|
||||||
*/
|
*/
|
@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Bootstrap;
|
namespace Koneko\VuexyAdmin\Application\Bootstrap\Registry;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
use Koneko\VuexyAdmin\Application\Contracts\Settings\SettingsRepositoryInterface;
|
use Koneko\VuexyAdmin\Application\Settings\Contracts\SettingsRepositoryInterface;
|
||||||
use Koneko\VuexyAdmin\Application\Cache\KonekoCacheManager;
|
use Koneko\VuexyAdmin\Application\Cache\KonekoCacheManager;
|
||||||
use Koneko\VuexyAdmin\Application\Loggers\{KonekoSystemLogger, KonekoSecurityLogger, KonekoUserInteractionLogger};
|
use Koneko\VuexyAdmin\Application\Loggers\{KonekoSystemLogger, KonekoSecurityLogger, KonekoUserInteractionLogger};
|
||||||
|
|
||||||
class KonekoComponentContextRegistrar
|
class ___KonekoComponentContextRegistrar
|
||||||
{
|
{
|
||||||
protected static ?string $currentComponent = null;
|
protected static ?string $currentComponent = null;
|
||||||
protected static ?string $currentSlug = null;
|
protected static ?string $currentSlug = null;
|
||||||
@ -24,11 +24,11 @@ class KonekoComponentContextRegistrar
|
|||||||
App::make(SettingsRepositoryInterface::class)
|
App::make(SettingsRepositoryInterface::class)
|
||||||
->setNamespaceByComponent($componentNamespace);
|
->setNamespaceByComponent($componentNamespace);
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
if (class_exists(KonekoCacheManager::class)) {
|
if (class_exists(KonekoCacheManager::class)) {
|
||||||
try {
|
try {
|
||||||
cache_manager($componentNamespace)->registerDefaults();
|
cache_m($componentNamespace)->registerDefaults();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
logger()->warning("[KonekoContext] No se pudo registrar defaults para CacheManager: {$e->getMessage()}");
|
logger()->warning("[KonekoContext] No se pudo registrar defaults para CacheManager: {$e->getMessage()}");
|
||||||
}
|
}
|
||||||
@ -45,6 +45,7 @@ class KonekoComponentContextRegistrar
|
|||||||
if (class_exists(KonekoUserInteractionLogger::class)) {
|
if (class_exists(KonekoUserInteractionLogger::class)) {
|
||||||
KonekoUserInteractionLogger::setComponent($componentNamespace, $slug);
|
KonekoUserInteractionLogger::setComponent($componentNamespace, $slug);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// Futuro: API Manager y Event Dispatcher
|
// Futuro: API Manager y Event Dispatcher
|
||||||
// api_manager()->setComponent($componentNamespace);
|
// api_manager()->setComponent($componentNamespace);
|
104
src/Application/Cache/Builders/KonekoAdminVarsBuilder copy.php
Normal file
104
src/Application/Cache/Builders/KonekoAdminVarsBuilder copy.php
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyAdmin\Application\Cache\Builders;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Koneko\VuexyAdmin\Application\CoreModule;
|
||||||
|
use Koneko\VuexyAdmin\Application\Cache\Services\KonekoVarsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🎛️ Builder de variables administrativas para el layout de administración.
|
||||||
|
* - Fuente primaria: settings globales (namespace 'koneko.core.layout.admin')
|
||||||
|
* - Permite override explícito por usuario autenticado.
|
||||||
|
*/
|
||||||
|
class KonekoAdminVarsBuilder
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected KonekoVarsService $vars
|
||||||
|
) {
|
||||||
|
$this->vars->context('layout', 'admin')->setKeyName('meta');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Devuelve las variables visuales del layout administrativo.
|
||||||
|
* Incluye título, autor, logos, favicon, etc.
|
||||||
|
*/
|
||||||
|
public function get(): array
|
||||||
|
{
|
||||||
|
return $this->vars->remember('meta', fn () => $this->resolveAdminVars());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limpia el caché asociado al layout administrativo.
|
||||||
|
*/
|
||||||
|
public function clear(): void
|
||||||
|
{
|
||||||
|
$this->vars->setKeyName('meta')->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Devuelve metainformación del contexto del builder (debug, auditoría).
|
||||||
|
*/
|
||||||
|
public function info(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'context' => $this->vars->getContext(),
|
||||||
|
'cache_key' => $this->vars->cacheKey('meta'),
|
||||||
|
'source' => 'config + settings + optional user override',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construye el array de variables administrativas del layout.
|
||||||
|
* Aplica override por usuario si está autenticado.
|
||||||
|
*/
|
||||||
|
protected function resolveAdminVars(): array
|
||||||
|
{
|
||||||
|
$base = settings()->context('layout', 'admin')->asArray()->getSubGroup();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'title' => $base['title'] ?? config_m()->get('layout.admin.title', 'Koneko Admin'),
|
||||||
|
'author' => $base['author'] ?? config_m()->get('layout.admin.author', 'Default Author'),
|
||||||
|
'description' => $base['description'] ?? config_m()->get('layout.admin.description', 'Default Description'),
|
||||||
|
'favicon' => $this->buildFaviconPaths($base),
|
||||||
|
'app_name' => $base['app_name'] ?? config_m()->get('app_name'),
|
||||||
|
'image_logo' => $this->buildImageLogoPaths($base),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construye el arreglo de rutas de favicon según configuración.
|
||||||
|
*/
|
||||||
|
protected function buildFaviconPaths(array $settings): array
|
||||||
|
{
|
||||||
|
$ns = $settings['favicon_ns'] ?? null;
|
||||||
|
$default = config_m()->get('favicon', 'favicon.ico');
|
||||||
|
|
||||||
|
return [
|
||||||
|
'namespace' => $ns,
|
||||||
|
'16x16' => $ns ? "{$ns}_16x16.png" : $default,
|
||||||
|
'76x76' => $ns ? "{$ns}_76x76.png" : $default,
|
||||||
|
'120x120' => $ns ? "{$ns}_120x120.png" : $default,
|
||||||
|
'152x152' => $ns ? "{$ns}_152x152.png" : $default,
|
||||||
|
'180x180' => $ns ? "{$ns}_180x180.png" : $default,
|
||||||
|
'192x192' => $ns ? "{$ns}_192x192.png" : $default,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construye el arreglo de rutas de logos según configuración.
|
||||||
|
*/
|
||||||
|
protected function buildImageLogoPaths(array $settings): array
|
||||||
|
{
|
||||||
|
$default = config_m()->get('app_logo', 'logo-default.png');
|
||||||
|
|
||||||
|
return [
|
||||||
|
'small' => $settings['image_logo_small'] ?? $default,
|
||||||
|
'medium' => $settings['image_logo_medium'] ?? $default,
|
||||||
|
'large' => $settings['image_logo'] ?? $default,
|
||||||
|
'small_dark' => $settings['image_logo_small_dark'] ?? $default,
|
||||||
|
'medium_dark' => $settings['image_logo_medium_dark'] ?? $default,
|
||||||
|
'large_dark' => $settings['image_logo_dark'] ?? $default,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
114
src/Application/Cache/Builders/KonekoAdminVarsBuilder.php
Normal file
114
src/Application/Cache/Builders/KonekoAdminVarsBuilder.php
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyAdmin\Application\Cache\Builders;
|
||||||
|
|
||||||
|
use Koneko\VuexyAdmin\Application\Settings\Contracts\SettingsRepositoryInterface;
|
||||||
|
use Koneko\VuexyAdmin\Application\Settings\Manager\KonekoSettingManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🎛️ Builder de variables administrativas para el layout de administración.
|
||||||
|
* - Fuente primaria: settings globales (namespace 'koneko.core.layout.admin')
|
||||||
|
* - Permite override explícito por usuario autenticado.
|
||||||
|
*/
|
||||||
|
class KonekoAdminVarsBuilder
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected SettingsRepositoryInterface $settings
|
||||||
|
) {
|
||||||
|
$this->settings->setGroup('layout');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Devuelve las variables visuales del layout administrativo.
|
||||||
|
* Incluye título, autor, logos, favicon, etc.
|
||||||
|
*/
|
||||||
|
public function get(): array
|
||||||
|
{
|
||||||
|
return $this->settings
|
||||||
|
->setSection('admin')
|
||||||
|
->setKeyName('meta')
|
||||||
|
->remember(fn () => $this->resolveAdminVars());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limpia el caché asociado al layout administrativo.
|
||||||
|
*/
|
||||||
|
public function clear(): void
|
||||||
|
{
|
||||||
|
$this->settings
|
||||||
|
->setSection('admin')
|
||||||
|
->forgetCache('meta');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Devuelve metainformación del contexto del builder (debug, auditoría).
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
public function info(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'context' => $this->settings->getContext(),
|
||||||
|
'cache_key' => $this->settings->cacheKey('meta'),
|
||||||
|
'source' => 'config + settings + optional user override',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construye el array de variables administrativas del layout.
|
||||||
|
* Aplica override por usuario si está autenticado.
|
||||||
|
*/
|
||||||
|
protected function resolveAdminVars(): array
|
||||||
|
{
|
||||||
|
$base = $this->settings
|
||||||
|
->setSection('admin')
|
||||||
|
->asArray()
|
||||||
|
->getSubGroup();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'title' => $base['title'] ?? config_m()->get('layout.admin.title', 'Koneko Admin'),
|
||||||
|
'author' => $base['author'] ?? config_m()->get('layout.admin.author', 'Default Author'),
|
||||||
|
'description' => $base['description'] ?? config_m()->get('layout.admin.description', 'Default Description'),
|
||||||
|
'favicon' => $this->buildFaviconPaths($base),
|
||||||
|
'app_name' => $base['app_name'] ?? config_m()->get('app_name', 'Koneko Admin'),
|
||||||
|
'image_logo' => $this->buildImageLogoPaths($base),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construye el arreglo de rutas de favicon según configuración.
|
||||||
|
*/
|
||||||
|
protected function buildFaviconPaths(array $settings): array
|
||||||
|
{
|
||||||
|
$ns = $settings['favicon_ns'] ?? null;
|
||||||
|
|
||||||
|
$default = config_m()->get('favicon', 'favicon.ico');
|
||||||
|
|
||||||
|
return [
|
||||||
|
'namespace' => $ns,
|
||||||
|
'16x16' => $ns ? "{$ns}_16x16.png" : $default,
|
||||||
|
'76x76' => $ns ? "{$ns}_76x76.png" : $default,
|
||||||
|
'120x120' => $ns ? "{$ns}_120x120.png" : $default,
|
||||||
|
'152x152' => $ns ? "{$ns}_152x152.png" : $default,
|
||||||
|
'180x180' => $ns ? "{$ns}_180x180.png" : $default,
|
||||||
|
'192x192' => $ns ? "{$ns}_192x192.png" : $default,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construye el arreglo de rutas de logos según configuración.
|
||||||
|
*/
|
||||||
|
protected function buildImageLogoPaths(array $settings): array
|
||||||
|
{
|
||||||
|
$default = config_m()->get('app_logo', 'logo-default.png');
|
||||||
|
|
||||||
|
return [
|
||||||
|
'small' => $settings['image_logo_small'] ?? $default,
|
||||||
|
'medium' => $settings['image_logo_medium'] ?? $default,
|
||||||
|
'large' => $settings['image_logo'] ?? $default,
|
||||||
|
'small_dark' => $settings['image_logo_small_dark'] ?? $default,
|
||||||
|
'medium_dark' => $settings['image_logo_medium_dark'] ?? $default,
|
||||||
|
'large_dark' => $settings['image_logo_dark'] ?? $default,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyAdmin\Application\Cache\Builders;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Koneko\VuexyAdmin\Application\CoreModule;
|
||||||
|
use Koneko\VuexyAdmin\Application\Cache\Services\KonekoVarsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🎛️ Builder de variables personalizadas para el layout de administración.
|
||||||
|
* - Fuente primaria: settings globales (namespace 'koneko.core.layout.admin')
|
||||||
|
* - Permite override explícito por usuario autenticado.
|
||||||
|
*/
|
||||||
|
class KonekoVuexyCustomizerVarsBuilder extends KonekoAdminVarsBuilder
|
||||||
|
{
|
||||||
|
public function build(): array
|
||||||
|
{
|
||||||
|
$this->setContext([
|
||||||
|
'component' => CoreModule::COMPONENT,
|
||||||
|
'group' => 'layout',
|
||||||
|
'sub_group' => 'vuexy',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->setScope('customizer');
|
||||||
|
|
||||||
|
return parent::build();
|
||||||
|
}
|
||||||
|
}
|
100
src/Application/Cache/Builders/SettingCacheKeyBuilder.php
Normal file
100
src/Application/Cache/Builders/SettingCacheKeyBuilder.php
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyAdmin\Application\Cache\Builders;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Auth\Authenticatable;
|
||||||
|
use Koneko\VuexyAdmin\Application\Settings\SettingDefaults;
|
||||||
|
use Koneko\VuexyAdmin\Application\CoreModule;
|
||||||
|
|
||||||
|
final class SettingCacheKeyBuilder
|
||||||
|
{
|
||||||
|
private const MAX_KEY_LENGTH = 120;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construye una clave canónica de cache para un setting.
|
||||||
|
*/
|
||||||
|
public static function build(
|
||||||
|
string $namespace,
|
||||||
|
string $environment = 'local',
|
||||||
|
?string $scope = null,
|
||||||
|
int|string|null $scopeId = null,
|
||||||
|
string $component = CoreModule::COMPONENT,
|
||||||
|
string $group = SettingDefaults::DEFAULT_GROUP,
|
||||||
|
string $section = SettingDefaults::DEFAULT_SECTION,
|
||||||
|
string $subGroup = SettingDefaults::DEFAULT_SUB_GROUP,
|
||||||
|
string $keyName
|
||||||
|
): string {
|
||||||
|
if (empty($keyName)) {
|
||||||
|
throw new \InvalidArgumentException("El parámetro 'keyName' no puede estar vacío.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$scopeSegment = $scopeId
|
||||||
|
? "{$scope}:{$scopeId}"
|
||||||
|
: ($scope ?? '');
|
||||||
|
|
||||||
|
$segments = array_filter([
|
||||||
|
$namespace,
|
||||||
|
$environment,
|
||||||
|
$scopeSegment,
|
||||||
|
$component,
|
||||||
|
$group,
|
||||||
|
$section,
|
||||||
|
$subGroup,
|
||||||
|
$keyName,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$baseKey = implode('.', $segments);
|
||||||
|
|
||||||
|
return strlen($baseKey) > self::MAX_KEY_LENGTH
|
||||||
|
? 'h:' . hash('sha1', $baseKey)
|
||||||
|
: $baseKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifica si una clave generada es calificada (tiene estructura válida).
|
||||||
|
*/
|
||||||
|
public static function isQualified(string $key): bool
|
||||||
|
{
|
||||||
|
return str_starts_with($key, 'h:') || substr_count($key, '.') >= 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construye una clave específica basada en un usuario autenticado.
|
||||||
|
*/
|
||||||
|
public static function forUser(
|
||||||
|
string $namespace,
|
||||||
|
Authenticatable|int|null $user,
|
||||||
|
string $environment = 'prod',
|
||||||
|
string $component = CoreModule::COMPONENT,
|
||||||
|
string $group = SettingDefaults::DEFAULT_GROUP,
|
||||||
|
string $section = SettingDefaults::DEFAULT_SECTION,
|
||||||
|
string $subGroup = SettingDefaults::DEFAULT_SUB_GROUP,
|
||||||
|
string $keyName,
|
||||||
|
): string {
|
||||||
|
if (empty($keyName)) {
|
||||||
|
throw new \InvalidArgumentException("El parámetro 'keyName' no puede estar vacío.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$userId = $user instanceof Authenticatable ? $user->getAuthIdentifier() : $user;
|
||||||
|
|
||||||
|
return self::build(
|
||||||
|
namespace: $namespace,
|
||||||
|
environment: $environment,
|
||||||
|
scope: 'user',
|
||||||
|
scopeId: $userId,
|
||||||
|
component: $component,
|
||||||
|
group: $group,
|
||||||
|
section: $section,
|
||||||
|
subGroup: $subGroup,
|
||||||
|
keyName: $keyName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Genera una clave hash predecible para valores extremadamente largos.
|
||||||
|
*/
|
||||||
|
public static function hashed(string ...$segments): string
|
||||||
|
{
|
||||||
|
return 'h:' . hash('sha1', implode('.', $segments));
|
||||||
|
}
|
||||||
|
}
|
@ -1,301 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Cache;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\{Config,DB,Redis};
|
|
||||||
use Memcached;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Servicio para gestionar y obtener información de configuración del sistema de caché.
|
|
||||||
*
|
|
||||||
* Esta clase proporciona métodos para obtener información detallada sobre las configuraciones
|
|
||||||
* de caché, sesión, base de datos y drivers del sistema. Permite consultar versiones,
|
|
||||||
* estados y configuraciones de diferentes servicios como Redis, Memcached y bases de datos.
|
|
||||||
*/
|
|
||||||
class CacheConfigService
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Obtiene la configuración completa del sistema de caché y servicios relacionados.
|
|
||||||
*
|
|
||||||
* @return array Configuración completa que incluye caché, sesión, base de datos y drivers
|
|
||||||
*/
|
|
||||||
public function getConfig(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'cache' => $this->getCacheConfig(),
|
|
||||||
'session' => $this->getSessionConfig(),
|
|
||||||
'database' => $this->getDatabaseConfig(),
|
|
||||||
'driver' => $this->getDriverVersion(),
|
|
||||||
'memcachedInUse' => $this->isDriverInUse('memcached'),
|
|
||||||
'redisInUse' => $this->isDriverInUse('redis'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtiene la configuración específica del sistema de caché.
|
|
||||||
*
|
|
||||||
* @return array Configuración del caché incluyendo driver, host y base de datos
|
|
||||||
*/
|
|
||||||
private function getCacheConfig(): array
|
|
||||||
{
|
|
||||||
$cacheConfig = Config::get('cache');
|
|
||||||
$driver = $cacheConfig['default'];
|
|
||||||
|
|
||||||
switch ($driver) {
|
|
||||||
case 'redis':
|
|
||||||
$connection = config('database.redis.cache');
|
|
||||||
$cacheConfig['host'] = $connection['host'] ?? 'localhost';
|
|
||||||
$cacheConfig['database'] = $connection['database'] ?? 'N/A';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'database':
|
|
||||||
$connection = config('database.connections.' . config('cache.stores.database.connection'));
|
|
||||||
$cacheConfig['host'] = $connection['host'] ?? 'localhost';
|
|
||||||
$cacheConfig['database'] = $connection['database'] ?? 'N/A';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'memcached':
|
|
||||||
$servers = config('cache.stores.memcached.servers');
|
|
||||||
$cacheConfig['host'] = $servers[0]['host'] ?? 'localhost';
|
|
||||||
$cacheConfig['database'] = 'N/A';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'file':
|
|
||||||
$cacheConfig['host'] = storage_path('framework/cache/data');
|
|
||||||
$cacheConfig['database'] = 'N/A';
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
$cacheConfig['host'] = 'N/A';
|
|
||||||
$cacheConfig['database'] = 'N/A';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $cacheConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtiene la configuración del sistema de sesiones.
|
|
||||||
*
|
|
||||||
* @return array Configuración de sesiones incluyendo driver, host y base de datos
|
|
||||||
*/
|
|
||||||
private function getSessionConfig(): array
|
|
||||||
{
|
|
||||||
$sessionConfig = Config::get('session');
|
|
||||||
$driver = $sessionConfig['driver'];
|
|
||||||
|
|
||||||
switch ($driver) {
|
|
||||||
case 'redis':
|
|
||||||
$connection = config('database.redis.sessions');
|
|
||||||
$sessionConfig['host'] = $connection['host'] ?? 'localhost';
|
|
||||||
$sessionConfig['database'] = $connection['database'] ?? 'N/A';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'database':
|
|
||||||
$connection = config('database.connections.' . $sessionConfig['connection']);
|
|
||||||
$sessionConfig['host'] = $connection['host'] ?? 'localhost';
|
|
||||||
$sessionConfig['database'] = $connection['database'] ?? 'N/A';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'memcached':
|
|
||||||
$servers = config('cache.stores.memcached.servers');
|
|
||||||
$sessionConfig['host'] = $servers[0]['host'] ?? 'localhost';
|
|
||||||
$sessionConfig['database'] = 'N/A';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'file':
|
|
||||||
$sessionConfig['host'] = storage_path('framework/sessions');
|
|
||||||
$sessionConfig['database'] = 'N/A';
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
$sessionConfig['host'] = 'N/A';
|
|
||||||
$sessionConfig['database'] = 'N/A';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $sessionConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtiene la configuración de la base de datos principal.
|
|
||||||
*
|
|
||||||
* @return array Configuración de la base de datos incluyendo host y nombre de la base de datos
|
|
||||||
*/
|
|
||||||
private function getDatabaseConfig(): array
|
|
||||||
{
|
|
||||||
$databaseConfig = Config::get('database');
|
|
||||||
$connection = $databaseConfig['default'];
|
|
||||||
|
|
||||||
$connectionConfig = config('database.connections.' . $connection);
|
|
||||||
$databaseConfig['host'] = $connectionConfig['host'] ?? 'localhost';
|
|
||||||
$databaseConfig['database'] = $connectionConfig['database'] ?? 'N/A';
|
|
||||||
|
|
||||||
return $databaseConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtiene información sobre las versiones de los drivers en uso.
|
|
||||||
*
|
|
||||||
* Recopila información detallada sobre las versiones de los drivers de base de datos,
|
|
||||||
* Redis y Memcached si están en uso en el sistema.
|
|
||||||
*
|
|
||||||
* @return array Información de versiones de los drivers activos
|
|
||||||
*/
|
|
||||||
private function getDriverVersion(): array
|
|
||||||
{
|
|
||||||
$drivers = [];
|
|
||||||
$defaultDatabaseDriver = config('database.default'); // Obtén el driver predeterminado
|
|
||||||
|
|
||||||
switch ($defaultDatabaseDriver) {
|
|
||||||
case 'mysql':
|
|
||||||
case 'mariadb':
|
|
||||||
$drivers['mysql'] = [
|
|
||||||
'version' => $this->getMySqlVersion(),
|
|
||||||
'details' => config("database.connections.$defaultDatabaseDriver"),
|
|
||||||
];
|
|
||||||
|
|
||||||
$drivers['mariadb'] = $drivers['mysql'];
|
|
||||||
|
|
||||||
case 'pgsql':
|
|
||||||
$drivers['pgsql'] = [
|
|
||||||
'version' => $this->getPgSqlVersion(),
|
|
||||||
'details' => config("database.connections.pgsql"),
|
|
||||||
];
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'sqlsrv':
|
|
||||||
$drivers['sqlsrv'] = [
|
|
||||||
'version' => $this->getSqlSrvVersion(),
|
|
||||||
'details' => config("database.connections.sqlsrv"),
|
|
||||||
];
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
$drivers['unknown'] = [
|
|
||||||
'version' => 'No disponible',
|
|
||||||
'details' => 'Driver no identificado',
|
|
||||||
];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Opcional: Agrega detalles de Redis y Memcached si están en uso
|
|
||||||
if ($this->isDriverInUse('redis')) {
|
|
||||||
$drivers['redis'] = [
|
|
||||||
'version' => $this->getRedisVersion(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->isDriverInUse('memcached')) {
|
|
||||||
$drivers['memcached'] = [
|
|
||||||
'version' => $this->getMemcachedVersion(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $drivers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtiene la versión del servidor MySQL.
|
|
||||||
*
|
|
||||||
* @return string Versión del servidor MySQL o mensaje de error
|
|
||||||
*/
|
|
||||||
private function getMySqlVersion(): string
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$version = DB::selectOne('SELECT VERSION() as version');
|
|
||||||
return $version->version ?? 'No disponible';
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return 'Error: ' . $e->getMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtiene la versión del servidor PostgreSQL.
|
|
||||||
*
|
|
||||||
* @return string Versión del servidor PostgreSQL o mensaje de error
|
|
||||||
*/
|
|
||||||
private function getPgSqlVersion(): string
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$version = DB::selectOne("SHOW server_version");
|
|
||||||
return $version->server_version ?? 'No disponible';
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return 'Error: ' . $e->getMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtiene la versión del servidor SQL Server.
|
|
||||||
*
|
|
||||||
* @return string Versión del servidor SQL Server o mensaje de error
|
|
||||||
*/
|
|
||||||
private function getSqlSrvVersion(): string
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$version = DB::selectOne("SELECT @@VERSION as version");
|
|
||||||
return $version->version ?? 'No disponible';
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return 'Error: ' . $e->getMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtiene la versión del servidor Memcached.
|
|
||||||
*
|
|
||||||
* @return string Versión del servidor Memcached o mensaje de error
|
|
||||||
*/
|
|
||||||
private function getMemcachedVersion(): string
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$memcached = new Memcached();
|
|
||||||
$memcached->addServer(
|
|
||||||
Config::get('cache.stores.memcached.servers.0.host'),
|
|
||||||
Config::get('cache.stores.memcached.servers.0.port')
|
|
||||||
);
|
|
||||||
|
|
||||||
$stats = $memcached->getStats();
|
|
||||||
foreach ($stats as $serverStats) {
|
|
||||||
return $serverStats['version'] ?? 'No disponible';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'No disponible';
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return 'Error: ' . $e->getMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtiene la versión del servidor Redis.
|
|
||||||
*
|
|
||||||
* @return string Versión del servidor Redis o mensaje de error
|
|
||||||
*/
|
|
||||||
private function getRedisVersion(): string
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$info = Redis::info();
|
|
||||||
return $info['redis_version'] ?? 'No disponible';
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return 'Error: ' . $e->getMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifica si un driver específico está en uso en el sistema.
|
|
||||||
*
|
|
||||||
* Comprueba si el driver está siendo utilizado en caché, sesiones o colas.
|
|
||||||
*
|
|
||||||
* @param string $driver Nombre del driver a verificar
|
|
||||||
* @return bool True si el driver está en uso, false en caso contrario
|
|
||||||
*/
|
|
||||||
protected function isDriverInUse(string $driver): bool
|
|
||||||
{
|
|
||||||
return in_array($driver, [
|
|
||||||
Config::get('cache.default'),
|
|
||||||
Config::get('session.driver'),
|
|
||||||
Config::get('queue.default'),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
67
src/Application/Cache/Contracts/CacheRepositoryInterface.php
Normal file
67
src/Application/Cache/Contracts/CacheRepositoryInterface.php
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyAdmin\Application\Cache\Contracts;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Auth\Authenticatable;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
interface CacheRepositoryInterface
|
||||||
|
{
|
||||||
|
// ==================== Factory ====================
|
||||||
|
|
||||||
|
public static function make(): static;
|
||||||
|
public static function fromArray(array $context): static;
|
||||||
|
public static function fromRequest(?Request $request = null): static;
|
||||||
|
|
||||||
|
// ==================== Context ====================
|
||||||
|
|
||||||
|
public function setNamespace(string $namespace): static;
|
||||||
|
public function setEnvironment(?string $environment = null): static;
|
||||||
|
public function setComponent(string $component): static;
|
||||||
|
|
||||||
|
public function context(string $group, ?string $section = null, ?string $subGroup = null): static;
|
||||||
|
public function setContextArray(array $context): static;
|
||||||
|
|
||||||
|
public function setScope(Model|string|false $scope, int|null|false $scopeId = false): static;
|
||||||
|
public function setScopeId(?int $scopeId): static;
|
||||||
|
public function setUser(Authenticatable|int|null|false $user): static;
|
||||||
|
public function withScopeFromModel(Model $model): static;
|
||||||
|
|
||||||
|
public function setGroup(string $group): static;
|
||||||
|
public function setSection(string $section): static;
|
||||||
|
public function setSubGroup(string $subGroup): static;
|
||||||
|
public function setKeyName(string $keyName): static;
|
||||||
|
|
||||||
|
// ==================== Config ====================
|
||||||
|
|
||||||
|
public function isEnabled(): bool;
|
||||||
|
public function resolveTTL(): int;
|
||||||
|
public function driver(): string;
|
||||||
|
|
||||||
|
// ==================== Cache Operations ====================
|
||||||
|
|
||||||
|
public function get(mixed $default = null): mixed;
|
||||||
|
public function put(mixed $value, ?int $ttl = null): void;
|
||||||
|
public function forget(): void;
|
||||||
|
|
||||||
|
public function remember(?callable $resolver = null, ?int $ttl = null): mixed;
|
||||||
|
public function rememberWithTTLResolution(callable $resolver, ?int $ttl = null): mixed;
|
||||||
|
|
||||||
|
// ==================== Getters ====================
|
||||||
|
|
||||||
|
public function qualifiedKey(?string $key = null): string;
|
||||||
|
public function getScopeModel(): ?Model;
|
||||||
|
|
||||||
|
// ==================== Utils ====================
|
||||||
|
|
||||||
|
public function has(string $qualifiedKey): bool;
|
||||||
|
public function hasContext(): bool;
|
||||||
|
public function reset(): static;
|
||||||
|
|
||||||
|
|
||||||
|
// ==================== Diagnostics ====================
|
||||||
|
|
||||||
|
public function info(): array;
|
||||||
|
public function infoWithCacheLayers(): array;
|
||||||
|
}
|
34
src/Application/Cache/Driver/KonekoCacheDriver.php
Normal file
34
src/Application/Cache/Driver/KonekoCacheDriver.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyAdmin\Application\Cache\Driver;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
|
final class KonekoCacheDriver
|
||||||
|
{
|
||||||
|
public static function get(string $key, mixed $default = null): mixed
|
||||||
|
{
|
||||||
|
return Cache::get($key, $default);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function put(string $key, mixed $value, int $ttl): void
|
||||||
|
{
|
||||||
|
Cache::put($key, $value, now()->addMinutes($ttl));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function forget(string $key): void
|
||||||
|
{
|
||||||
|
Cache::forget($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function remember(string $key, int $ttl, \Closure $callback): mixed
|
||||||
|
{
|
||||||
|
return Cache::remember($key, now()->addMinutes($ttl), $callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function has(string $key): bool
|
||||||
|
{
|
||||||
|
return Cache::has($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,186 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Cache;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 📊 Gestor de Cache del Ecosistema Koneko
|
|
||||||
* Soporte para múltiples niveles (core, componente, grupo), drivers mixtos y tagging.
|
|
||||||
* Compatible con redis, memcached, file y database.
|
|
||||||
*/
|
|
||||||
class KonekoCacheManager
|
|
||||||
{
|
|
||||||
private string $namespace;
|
|
||||||
private string $component;
|
|
||||||
private string $group;
|
|
||||||
private string $subGroup;
|
|
||||||
|
|
||||||
public function __construct(string $namespace, string $component = 'core')
|
|
||||||
{
|
|
||||||
$this->namespace = $namespace;
|
|
||||||
$this->component = $component;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setContext(string $component, string $group, string $subGroup): static
|
|
||||||
{
|
|
||||||
return $this
|
|
||||||
->setComponent($component)
|
|
||||||
->setGroup($group)
|
|
||||||
->setSubGroup($subGroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setNamespace(string $namespace): static
|
|
||||||
{
|
|
||||||
$this->validateSlug('namespace', $namespace);
|
|
||||||
|
|
||||||
$this->namespace = strtolower($namespace);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setComponent(string $component): static
|
|
||||||
{
|
|
||||||
$this->validateSlug('component', $component);
|
|
||||||
|
|
||||||
$this->component = strtolower($component);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setGroup(string $group): static
|
|
||||||
{
|
|
||||||
$this->validateSlug('group', $group);
|
|
||||||
|
|
||||||
$this->group = strtolower($group);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setSubGroup(string $subGroup): static
|
|
||||||
{
|
|
||||||
$this->validateSlug('subGroup', $subGroup);
|
|
||||||
|
|
||||||
$this->subGroup = strtolower($subGroup);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private function ensureContext(): void
|
|
||||||
{
|
|
||||||
foreach (['component', 'group', 'subGroup'] as $context) {
|
|
||||||
if (empty($this->$context)) {
|
|
||||||
throw new \LogicException("Debe establecer {$context} antes de generar una clave de caché.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function currentNamespace(): string
|
|
||||||
{
|
|
||||||
return $this->namespace;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function currentComponent(): string
|
|
||||||
{
|
|
||||||
return $this->component;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function currentGroup(): string
|
|
||||||
{
|
|
||||||
return $this->group;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function currentSubGroup(): string
|
|
||||||
{
|
|
||||||
return $this->subGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function fullKey(string $suffix): string
|
|
||||||
{
|
|
||||||
return "{$this->path()}.{$suffix}";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function key(string $suffix): string
|
|
||||||
{
|
|
||||||
$this->ensureContext();
|
|
||||||
|
|
||||||
return "{$this->path()}.{$suffix}";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function config(string $key, mixed $default = null): mixed
|
|
||||||
{
|
|
||||||
return config($this->key($key), $default);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function ttl(): int
|
|
||||||
{
|
|
||||||
return (int) (
|
|
||||||
config("{$this->namespace}.{$this->component}.{$this->group}.{$this->subGroup}.ttl")
|
|
||||||
?? config("{$this->namespace}.{$this->component}.{$this->group}.ttl")
|
|
||||||
?? config("{$this->namespace}.{$this->component}.cache.ttl")
|
|
||||||
?? config("{$this->namespace}.cache.ttl", 3600)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function enabled(): bool
|
|
||||||
{
|
|
||||||
return (bool) (
|
|
||||||
config("{$this->namespace}.{$this->component}.{$this->group}.{$this->subGroup}.enabled")
|
|
||||||
?? config("{$this->namespace}.{$this->component}.{$this->group}.enabled")
|
|
||||||
?? config("{$this->namespace}.{$this->component}.cache.enabled")
|
|
||||||
?? config("{$this->namespace}.cache.enabled", true)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function shouldDebug(): bool
|
|
||||||
{
|
|
||||||
return (bool) $this->config('debug', false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function driver(): string
|
|
||||||
{
|
|
||||||
return config('cache.default');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function path(): string
|
|
||||||
{
|
|
||||||
return "{$this->namespace}.{$this->component}.{$this->group}.{$this->subGroup}";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function info(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'namespace' => $this->namespace,
|
|
||||||
'component' => $this->component,
|
|
||||||
'group' => $this->group,
|
|
||||||
'subGroup' => $this->subGroup,
|
|
||||||
'enabled' => $this->enabled(),
|
|
||||||
'ttl' => $this->ttl(),
|
|
||||||
'driver' => $this->driver(),
|
|
||||||
'debug' => $this->shouldDebug(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function validateSlug(string $field, string $value): void
|
|
||||||
{
|
|
||||||
if (!preg_match('/^[a-z0-9\-]+$/', $value)) {
|
|
||||||
throw new \InvalidArgumentException("El valor de '{$field}' debe ser un slug válido.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function ensureContext(): void
|
|
||||||
{
|
|
||||||
if (empty($this->component) || empty($this->group)) {
|
|
||||||
throw new \LogicException("Debe establecer component y group antes de generar una clave de caché.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,185 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Cache;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 📊 Gestor de Cache del Ecosistema Koneko
|
|
||||||
* Soporte para múltiples niveles (core, componente, grupo), drivers mixtos y tagging.
|
|
||||||
* Compatible con redis, memcached, file y database.
|
|
||||||
*/
|
|
||||||
class KonekoCacheManager
|
|
||||||
{
|
|
||||||
private string $namespace;
|
|
||||||
private string $component;
|
|
||||||
private string $group;
|
|
||||||
|
|
||||||
public function __construct(string $namespace)
|
|
||||||
{
|
|
||||||
$this->namespace = $namespace;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Establece el contexto de la caché.
|
|
||||||
*/
|
|
||||||
public function setContext(string $component, string $group): static
|
|
||||||
{
|
|
||||||
return $this
|
|
||||||
->setNamespace($this->namespace)
|
|
||||||
->setComponent($component)
|
|
||||||
->setGroup($group);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Establece el namespace de la caché.
|
|
||||||
*/
|
|
||||||
public function setNamespace(string $namespace): static
|
|
||||||
{
|
|
||||||
if (!preg_match('/^[a-z0-9\-]+$/', $namespace)) {
|
|
||||||
throw new \InvalidArgumentException("El namespace '{$namespace}' debe ser un slug válido.");
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->namespace = strtolower($namespace);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Establece el componente de la caché.
|
|
||||||
*/
|
|
||||||
public function setComponent(string $component): static
|
|
||||||
{
|
|
||||||
if (!preg_match('/^[a-z0-9\-]+$/', $component)) {
|
|
||||||
throw new \InvalidArgumentException("El componente '{$component}' debe ser un slug válido.");
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->component = strtolower($component);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Establece el grupo de la caché.
|
|
||||||
*/
|
|
||||||
public function setGroup(string $group): static
|
|
||||||
{
|
|
||||||
if (!preg_match('/^[a-z0-9\-]+$/', $group)) {
|
|
||||||
throw new \InvalidArgumentException("El grupo '{$group}' debe ser un slug válido.");
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->group = strtolower($group);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function currentNamespace(): string
|
|
||||||
{
|
|
||||||
return $this->namespace;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function currentComponent(): string
|
|
||||||
{
|
|
||||||
return $this->component;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function currentGroup(): string
|
|
||||||
{
|
|
||||||
return $this->group;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Genera una clave calificada para la caché.
|
|
||||||
*/
|
|
||||||
public function key(string $suffix): string
|
|
||||||
{
|
|
||||||
if (empty($this->component) || empty($this->group)) {
|
|
||||||
throw new \LogicException("Component and group must be set before generating a cache key.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return "{$this->path()}.{$suffix}";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtiene un valor de configuración.
|
|
||||||
*/
|
|
||||||
public function config(string $key, mixed $default = null): mixed
|
|
||||||
{
|
|
||||||
return config($this->key($key), $default);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtiene el tiempo de vida (TTL) de la caché.
|
|
||||||
*/
|
|
||||||
public function ttl(): int
|
|
||||||
{
|
|
||||||
return (int) (
|
|
||||||
config("{$this->namespace}.{$this->component}.{$this->group}.ttl") ??
|
|
||||||
config("{$this->namespace}.{$this->component}.cache.ttl") ??
|
|
||||||
config("{$this->namespace}.cache.ttl", 3600)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtiene el estado de habilitación de la caché.
|
|
||||||
*/
|
|
||||||
public function enabled(): bool
|
|
||||||
{
|
|
||||||
return (bool) (
|
|
||||||
config("{$this->namespace}.{$this->component}.{$this->group}.enabled") ??
|
|
||||||
config("{$this->namespace}.{$this->component}.cache.enabled") ??
|
|
||||||
config("{$this->namespace}.cache.enabled", true)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determina si se debe depurar la caché.
|
|
||||||
*/
|
|
||||||
public function shouldDebug(): bool
|
|
||||||
{
|
|
||||||
return (bool) $this->config('debug', false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtiene el driver de caché.
|
|
||||||
*/
|
|
||||||
public function driver(): string
|
|
||||||
{
|
|
||||||
return config('cache.default');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registra los valores por defecto en la configuración.
|
|
||||||
*/
|
|
||||||
public function registerDefaults(): void
|
|
||||||
{
|
|
||||||
if (! config()->has($this->key('ttl'))) {
|
|
||||||
config()->set($this->key('ttl'), 3600);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! config()->has($this->key('enabled'))) {
|
|
||||||
config()->set($this->key('enabled'), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtiene la ruta de la caché.
|
|
||||||
*/
|
|
||||||
public function path(): string
|
|
||||||
{
|
|
||||||
return "{$this->namespace}.{$this->component}.{$this->group}";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Información extendida de depuración.
|
|
||||||
*/
|
|
||||||
public function info(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'component' => $this->component,
|
|
||||||
'group' => $this->group,
|
|
||||||
'enabled' => $this->enabled(),
|
|
||||||
'ttl' => $this->ttl(),
|
|
||||||
'driver' => $this->driver(),
|
|
||||||
'debug' => $this->shouldDebug(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,232 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Cache;
|
|
||||||
|
|
||||||
use Illuminate\Contracts\Auth\Authenticatable;
|
|
||||||
use Illuminate\Support\Facades\{Auth, Config};
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Koneko\VuexyAdmin\Application\Enums\Settings\SettingScope;
|
|
||||||
use Koneko\VuexyAdmin\Models\User;
|
|
||||||
|
|
||||||
class KonekoCacheManager
|
|
||||||
{
|
|
||||||
public const MAX_KEY_LENGTH = 120; // Friendly limit
|
|
||||||
private const DEFAULT_SCOPE = SettingScope::GLOBAL->value;
|
|
||||||
private const USER_GUEST_ALIAS = SettingScope::GUEST->value;
|
|
||||||
|
|
||||||
private string $namespace;
|
|
||||||
private string $scope;
|
|
||||||
|
|
||||||
private string $component;
|
|
||||||
private string $group;
|
|
||||||
private string $subGroup;
|
|
||||||
|
|
||||||
protected bool $isUserScoped = false;
|
|
||||||
protected ?Authenticatable $user = null;
|
|
||||||
|
|
||||||
public function __construct(string $namespace)
|
|
||||||
{
|
|
||||||
if (empty($namespace) || !preg_match('/^[a-z0-9\-]+$/', $namespace)) {
|
|
||||||
throw new \InvalidArgumentException("El namespace '{$namespace}' no es válido.");
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->namespace = $this->truncate('namespace', $namespace, 8);
|
|
||||||
$this->scope = self::DEFAULT_SCOPE;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ========= CONTEXT MANAGEMENT =========
|
|
||||||
|
|
||||||
public function setContext(
|
|
||||||
string $component,
|
|
||||||
string $group,
|
|
||||||
string $subGroup,
|
|
||||||
string $scope = self::DEFAULT_SCOPE
|
|
||||||
): static {
|
|
||||||
return $this
|
|
||||||
->setComponent($component)
|
|
||||||
->setGroup($group)
|
|
||||||
->setSubGroup($subGroup)
|
|
||||||
->setScope($scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setScope(SettingScope|string $scope): static
|
|
||||||
{
|
|
||||||
if (is_string($scope) && !SettingScope::isValid($scope)) {
|
|
||||||
throw new \InvalidArgumentException("Scope '{$scope}' no es válido.");
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->scope = is_string($scope)
|
|
||||||
? SettingScope::from($scope)->value
|
|
||||||
: $scope->value;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function setNamespace(string $namespace): static
|
|
||||||
{
|
|
||||||
$this->namespace = $this->truncate('namespace', $namespace, 8);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setComponent(string $component): static
|
|
||||||
{
|
|
||||||
$this->component = $this->truncate('component', $component, 16);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setGroup(string $group): static
|
|
||||||
{
|
|
||||||
$this->group = $this->truncate('group', $group, 16);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setSubGroup(string $subGroup): static
|
|
||||||
{
|
|
||||||
$this->subGroup = $this->truncate('subGroup', $subGroup, 16);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setUser(int|Authenticatable|null|false $user = null): static
|
|
||||||
{
|
|
||||||
match (true) {
|
|
||||||
$user === false => $this->resetUserScope(), // Visitante explícito
|
|
||||||
is_int($user) => $this->assignUser(User::findOrFail($user)),
|
|
||||||
$user instanceof Authenticatable => $this->assignUser($user),
|
|
||||||
default => $this->assignUser(Auth::user()) // Usuario autenticado o null (visitante)
|
|
||||||
};
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function assignUser(?Authenticatable $user): void
|
|
||||||
{
|
|
||||||
$this->user = $user;
|
|
||||||
$this->isUserScoped = !is_null($user);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function resetUserScope(): void
|
|
||||||
{
|
|
||||||
$this->user = null;
|
|
||||||
$this->isUserScoped = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isUserScoped(): bool
|
|
||||||
{
|
|
||||||
return $this->isUserScoped;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========= ACCESSORS =========
|
|
||||||
|
|
||||||
public function currentNamespace(): string { return $this->namespace; }
|
|
||||||
public function currentComponent(): string { return $this->component; }
|
|
||||||
public function currentGroup(): string { return $this->group; }
|
|
||||||
public function currentSubGroup(): string { return $this->subGroup; }
|
|
||||||
public function currentScope(): string { return $this->scope; }
|
|
||||||
|
|
||||||
// ========= CACHE CONFIGURATION =========
|
|
||||||
|
|
||||||
public function key(string $suffix): string
|
|
||||||
{
|
|
||||||
$this->ensureContext();
|
|
||||||
|
|
||||||
$userSegment = $this->isUserScoped
|
|
||||||
? 'u.' . ($this->user?->getAuthIdentifier() ?? self::USER_GUEST_ALIAS)
|
|
||||||
: self::DEFAULT_SCOPE;
|
|
||||||
|
|
||||||
$scopeSegment = $this->scope === self::DEFAULT_SCOPE
|
|
||||||
? null
|
|
||||||
: "scope." . $this->scope;
|
|
||||||
|
|
||||||
$base = implode('.', array_filter([
|
|
||||||
$this->namespace,
|
|
||||||
app()->environment(),
|
|
||||||
$this->component,
|
|
||||||
$this->group,
|
|
||||||
$this->subGroup,
|
|
||||||
$scopeSegment,
|
|
||||||
$userSegment,
|
|
||||||
$suffix
|
|
||||||
]));
|
|
||||||
|
|
||||||
return strlen($base) > self::MAX_KEY_LENGTH
|
|
||||||
? 'h:' . crc32($base)
|
|
||||||
: $base;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function ttl(): int
|
|
||||||
{
|
|
||||||
return (int) (
|
|
||||||
Config::get("{$this->path()}.ttl") ??
|
|
||||||
Config::get("{$this->namespace}.{$this->component}.{$this->group}.ttl") ??
|
|
||||||
Config::get("{$this->namespace}.{$this->component}.cache.ttl") ??
|
|
||||||
Config::get("{$this->namespace}.cache.ttl", 3600)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function enabled(): bool
|
|
||||||
{
|
|
||||||
return (bool) (
|
|
||||||
Config::get("{$this->path()}.enabled") ??
|
|
||||||
Config::get("{$this->namespace}.{$this->component}.{$this->group}.enabled") ??
|
|
||||||
Config::get("{$this->namespace}.{$this->component}.cache.enabled") ??
|
|
||||||
Config::get("{$this->namespace}.cache.enabled", true)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function driver(): string
|
|
||||||
{
|
|
||||||
return Config::get('cache.default');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function path(): string
|
|
||||||
{
|
|
||||||
return "{$this->namespace}." . app()->environment() . ".{$this->component}.{$this->group}.{$this->subGroup}";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function info(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'environment' => app()->environment(),
|
|
||||||
'namespace' => $this->namespace,
|
|
||||||
'component' => $this->component,
|
|
||||||
'group' => $this->group,
|
|
||||||
'subGroup' => $this->subGroup,
|
|
||||||
'scope' => $this->scope,
|
|
||||||
'enabled' => $this->enabled(),
|
|
||||||
'ttl' => $this->ttl(),
|
|
||||||
'driver' => $this->driver(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========= VALIDATION & SANITIZATION =========
|
|
||||||
|
|
||||||
private function validateSlug(string $field, string $value): void
|
|
||||||
{
|
|
||||||
if (!preg_match('/^[a-z0-9\-]+$/', $value)) {
|
|
||||||
throw new \InvalidArgumentException("El valor de '{$field}' debe ser un slug válido.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function ensureContext(): void
|
|
||||||
{
|
|
||||||
foreach (['namespace', 'component', 'group', 'subGroup'] as $prop) {
|
|
||||||
if (empty($this->$prop) || !preg_match('/^[a-z0-9\-]+$/', $this->$prop)) {
|
|
||||||
throw new \InvalidArgumentException("El valor de '{$prop}' es obligatorio y debe ser un slug válido.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private function truncate(string $field, string $value, int $maxLength): string
|
|
||||||
{
|
|
||||||
$this->validateSlug($field, $value);
|
|
||||||
|
|
||||||
return Str::limit(strtolower($value), $maxLength, '');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,155 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Cache;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Illuminate\Support\Facades\Redis;
|
|
||||||
|
|
||||||
class KonekoSessionManager
|
|
||||||
{
|
|
||||||
private string $driver;
|
|
||||||
|
|
||||||
public function __construct(mixed $driver = null)
|
|
||||||
{
|
|
||||||
$this->driver = $driver ?? config('session.driver');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSessionStats(mixed $driver = null): array
|
|
||||||
{
|
|
||||||
$driver = $driver ?? $this->driver;
|
|
||||||
|
|
||||||
if (!$this->isSupportedDriver($driver))
|
|
||||||
return $this->response('warning', 'Driver no soportado o no configurado.', ['session_count' => 0]);
|
|
||||||
|
|
||||||
try {
|
|
||||||
switch ($driver) {
|
|
||||||
case 'redis':
|
|
||||||
return $this->getRedisStats();
|
|
||||||
|
|
||||||
case 'database':
|
|
||||||
return $this->getDatabaseStats();
|
|
||||||
|
|
||||||
case 'file':
|
|
||||||
return $this->getFileStats();
|
|
||||||
|
|
||||||
default:
|
|
||||||
return $this->response('warning', 'Driver no reconocido.', ['session_count' => 0]);
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return $this->response('danger', 'Error al obtener estadísticas: ' . $e->getMessage(), ['session_count' => 0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function clearSessions(mixed $driver = null): array
|
|
||||||
{
|
|
||||||
$driver = $driver ?? $this->driver;
|
|
||||||
|
|
||||||
if (!$this->isSupportedDriver($driver)) {
|
|
||||||
return $this->response('warning', 'Driver no soportado o no configurado.');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
switch ($driver) {
|
|
||||||
case 'redis':
|
|
||||||
return $this->clearRedisSessions();
|
|
||||||
|
|
||||||
case 'memcached':
|
|
||||||
Cache::getStore()->flush();
|
|
||||||
return $this->response('success', 'Se eliminó la memoria caché de sesiones en Memcached.');
|
|
||||||
|
|
||||||
case 'database':
|
|
||||||
DB::table('sessions')->truncate();
|
|
||||||
return $this->response('success', 'Se eliminó la memoria caché de sesiones en la base de datos.');
|
|
||||||
|
|
||||||
case 'file':
|
|
||||||
return $this->clearFileSessions();
|
|
||||||
|
|
||||||
default:
|
|
||||||
return $this->response('warning', 'Driver no reconocido.');
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return $this->response('danger', 'Error al limpiar las sesiones: ' . $e->getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private function getRedisStats()
|
|
||||||
{
|
|
||||||
$prefix = config('cache.prefix'); // Asegúrate de agregar el sufijo correcto si es necesario
|
|
||||||
$keys = Redis::connection('sessions')->keys($prefix . '*');
|
|
||||||
|
|
||||||
return $this->response('success', 'Se ha recargado la información de la caché de Redis.', ['session_count' => count($keys)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getDatabaseStats(): array
|
|
||||||
{
|
|
||||||
$sessionCount = DB::table('sessions')->count();
|
|
||||||
|
|
||||||
return $this->response('success', 'Se ha recargado la información de la base de datos.', ['session_count' => $sessionCount]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getFileStats(): array
|
|
||||||
{
|
|
||||||
$cachePath = config('session.files');
|
|
||||||
$files = glob($cachePath . '/*');
|
|
||||||
|
|
||||||
return $this->response('success', 'Se ha recargado la información de sesiones de archivos.', ['session_count' => count($files)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Limpia sesiones en Redis.
|
|
||||||
*/
|
|
||||||
private function clearRedisSessions(): array
|
|
||||||
{
|
|
||||||
$prefix = config('cache.prefix', '');
|
|
||||||
$keys = Redis::connection('sessions')->keys($prefix . '*');
|
|
||||||
|
|
||||||
if (!empty($keys)) {
|
|
||||||
Redis::connection('sessions')->flushdb();
|
|
||||||
|
|
||||||
// Simulate cache clearing delay
|
|
||||||
sleep(1);
|
|
||||||
|
|
||||||
return $this->response('success', 'Se eliminó la memoria caché de sesiones en Redis.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->response('info', 'No se encontraron claves para eliminar en Redis.');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Limpia sesiones en archivos.
|
|
||||||
*/
|
|
||||||
private function clearFileSessions(): array
|
|
||||||
{
|
|
||||||
$cachePath = config('session.files');
|
|
||||||
$files = glob($cachePath . '/*');
|
|
||||||
|
|
||||||
if (!empty($files)) {
|
|
||||||
foreach ($files as $file) {
|
|
||||||
unlink($file);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->response('success', 'Se eliminó la memoria caché de sesiones en archivos.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->response('info', 'No se encontraron sesiones en archivos para eliminar.');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private function isSupportedDriver(string $driver): bool
|
|
||||||
{
|
|
||||||
return in_array($driver, ['redis', 'memcached', 'database', 'file']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Genera una respuesta estandarizada.
|
|
||||||
*/
|
|
||||||
private function response(string $status, string $message, array $data = []): array
|
|
||||||
{
|
|
||||||
return array_merge(compact('status', 'message'), $data);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyAdmin\Application\Cache\Manager\Concerns;
|
||||||
|
|
||||||
|
use Koneko\VuexyAdmin\Application\Traits\System\Context\HasBaseContext;
|
||||||
|
use Koneko\VuexyAdmin\Application\Traits\System\Context\HasCacheContextValidation;
|
||||||
|
|
||||||
|
trait ___HasCacheContext
|
||||||
|
{
|
||||||
|
use HasBaseContext;
|
||||||
|
use HasCacheContextValidation;
|
||||||
|
|
||||||
|
// ======================= HELPERS =========================
|
||||||
|
|
||||||
|
public function reset(): static
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
181
src/Application/Cache/Manager/KonekoCacheManager.php
Normal file
181
src/Application/Cache/Manager/KonekoCacheManager.php
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyAdmin\Application\Cache\Manager;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Config;
|
||||||
|
use Koneko\VuexyAdmin\Application\Cache\Driver\KonekoCacheDriver;
|
||||||
|
use Koneko\VuexyAdmin\Application\Cache\Contracts\CacheRepositoryInterface;
|
||||||
|
use Koneko\VuexyAdmin\Application\Traits\System\Context\{HasBaseContext, HasCacheContextValidation};
|
||||||
|
use Koneko\VuexyAdmin\Application\CoreModule;
|
||||||
|
|
||||||
|
final class KonekoCacheManager implements CacheRepositoryInterface
|
||||||
|
{
|
||||||
|
use HasBaseContext;
|
||||||
|
use HasCacheContextValidation;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->setNamespace(CoreModule::NAMESPACE)
|
||||||
|
->setEnvironment()
|
||||||
|
->setComponent(CoreModule::COMPONENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Factory ====================
|
||||||
|
|
||||||
|
public static function make(): static
|
||||||
|
{
|
||||||
|
return new static();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================= ⚙️ Configuración de Cache =========================
|
||||||
|
public function isEnabled(): bool
|
||||||
|
{
|
||||||
|
foreach ($this->enabledCandidateKeys() as $key) {
|
||||||
|
$value = Config::get($key);
|
||||||
|
if (!is_null($value)) {
|
||||||
|
return (bool) $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resolveEnabledSourceKey(): string
|
||||||
|
{
|
||||||
|
foreach ($this->enabledCandidateKeys() as $key) {
|
||||||
|
if (Config::has($key)) {
|
||||||
|
return $key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'default';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resolveTTL(): int
|
||||||
|
{
|
||||||
|
foreach ($this->ttlCandidateKeys() as $key) {
|
||||||
|
if (Config::has($key)) {
|
||||||
|
return (int) Config::get($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 3600;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resolveTtlSourceKey(): string
|
||||||
|
{
|
||||||
|
foreach ($this->ttlCandidateKeys() as $key) {
|
||||||
|
if (Config::has($key)) {
|
||||||
|
return $key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'default';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function driver(): string
|
||||||
|
{
|
||||||
|
return config('cache.default');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function enabledCandidateKeys(): array
|
||||||
|
{
|
||||||
|
$base = "{$this->context['namespace']}.{$this->context['component']}.{$this->context['group']}";
|
||||||
|
|
||||||
|
return [
|
||||||
|
"{$this->context['namespace']}.cache.enabled",
|
||||||
|
"{$this->context['namespace']}.{$this->context['component']}.cache.enabled",
|
||||||
|
"{$base}.cache.enabled",
|
||||||
|
"{$base}.{$this->context['sub_group']}.cache.enabled",
|
||||||
|
"{$base}.{$this->context['section']}.{$this->context['sub_group']}.cache.enabled",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function ttlCandidateKeys(): array
|
||||||
|
{
|
||||||
|
$base = "{$this->context['namespace']}.{$this->context['component']}.{$this->context['group']}";
|
||||||
|
|
||||||
|
return [
|
||||||
|
"{$base}.{$this->context['sub_group']}.ttl",
|
||||||
|
"{$base}.ttl",
|
||||||
|
"{$this->context['namespace']}.{$this->context['component']}.cache.ttl",
|
||||||
|
"{$this->context['namespace']}.cache.ttl",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================= 🧠 Operaciones =========================
|
||||||
|
|
||||||
|
public function get(mixed $default = null): mixed
|
||||||
|
{
|
||||||
|
return KonekoCacheDriver::get($this->qualifiedKey(), $default);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function put(mixed $value, ?int $ttl = null): void
|
||||||
|
{
|
||||||
|
KonekoCacheDriver::put($this->qualifiedKey(), $value, $ttl ?? $this->resolveTTL());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function forget(): void
|
||||||
|
{
|
||||||
|
KonekoCacheDriver::forget($this->qualifiedKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function remember(?callable $resolver = null, ?int $ttl = null): mixed
|
||||||
|
{
|
||||||
|
return $this->rememberWithTTLResolution($resolver ?? fn () => null, $ttl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rememberWithTTLResolution(callable $resolver, ?int $ttl = null): mixed
|
||||||
|
{
|
||||||
|
return KonekoCacheDriver::remember(
|
||||||
|
$this->qualifiedKey(),
|
||||||
|
$ttl ?? $this->resolveTTL(),
|
||||||
|
$resolver
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================= 🧩 Contexto =========================
|
||||||
|
|
||||||
|
public function has(string $qualifiedKey): bool
|
||||||
|
{
|
||||||
|
return KonekoCacheDriver::has($qualifiedKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasContext(): bool
|
||||||
|
{
|
||||||
|
return isset($this->context['component'], $this->context['group'], $this->context['sub_group'], $this->context['key_name']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reset(): static
|
||||||
|
{
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================= 🧪 Diagnóstico =========================
|
||||||
|
|
||||||
|
public function info(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'key' => $this->qualifiedKey(),
|
||||||
|
'context' => $this->context,
|
||||||
|
'enabled' => $this->isEnabled(),
|
||||||
|
'ttl' => $this->resolveTTL(),
|
||||||
|
'driver' => $this->driver(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function infoWithCacheLayers(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'context' => $this->context,
|
||||||
|
'qualified_key' => $this->qualifiedKey(),
|
||||||
|
'enabled' => $this->isEnabled(),
|
||||||
|
'enabled_source' => $this->resolveEnabledSourceKey(),
|
||||||
|
'ttl' => $this->resolveTTL(),
|
||||||
|
'ttl_source' => $this->resolveTtlSourceKey(),
|
||||||
|
'driver' => $this->driver(),
|
||||||
|
'has' => $this->has($this->qualifiedKey()),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Cache;
|
namespace Koneko\VuexyAdmin\Application\Cache\Manager;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\{Cache,DB,Redis,File};
|
use Illuminate\Support\Facades\{Cache, DB, Redis, File};
|
||||||
use Memcached;
|
use Memcached;
|
||||||
|
|
||||||
/**
|
/**
|
131
src/Application/Cache/Services/KonekoVarsService.php
Normal file
131
src/Application/Cache/Services/KonekoVarsService.php
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyAdmin\Application\Cache\Services;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Contracts\Auth\Authenticatable;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Koneko\VuexyAdmin\Application\CoreModule;
|
||||||
|
use Koneko\VuexyAdmin\Application\Cache\Manager\KonekoCacheManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🎛️ Servicio central para construcción de variables visuales Koneko.
|
||||||
|
* Cachea resultados en memoria, permite resolución flexible desde settings + config.
|
||||||
|
*
|
||||||
|
* @method self context(string $group, string $section, string|null $subGroup = null)
|
||||||
|
* @method self setKeyName(string $key)
|
||||||
|
* @method self forScope(string $scope, int $scope_id)
|
||||||
|
* @method self forModel(Model $model)
|
||||||
|
* @method self forUser(Authenticatable|int|null $user)
|
||||||
|
*/
|
||||||
|
class KonekoVarsService
|
||||||
|
{
|
||||||
|
protected string $namespace = CoreModule::NAMESPACE;
|
||||||
|
protected string $component = CoreModule::COMPONENT;
|
||||||
|
|
||||||
|
protected string $group;
|
||||||
|
protected string $section;
|
||||||
|
protected ?string $subGroup = null;
|
||||||
|
protected ?string $keyName = null;
|
||||||
|
|
||||||
|
protected ?string $scope = null;
|
||||||
|
protected ?int $scope_id = null;
|
||||||
|
|
||||||
|
public function context(string $group, string $section, ?string $subGroup = null): static
|
||||||
|
{
|
||||||
|
$this->group = $group;
|
||||||
|
$this->section = $section;
|
||||||
|
$this->subGroup = $subGroup;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setKeyName(string $key): static
|
||||||
|
{
|
||||||
|
$this->keyName = $key;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function forScope(string $scope, int $scope_id): static
|
||||||
|
{
|
||||||
|
$this->scope = $scope;
|
||||||
|
$this->scope_id = $scope_id;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function forModel(Model $model): static
|
||||||
|
{
|
||||||
|
$this->scope = strtolower(class_basename($model));
|
||||||
|
$this->scope_id = $model->getKey();
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function forUser(Authenticatable|int|null $user): static
|
||||||
|
{
|
||||||
|
$this->scope = 'user';
|
||||||
|
$this->scope_id = is_int($user) ? $user : ($user?->getAuthIdentifier());
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ejecuta y cachea un resultado asociado al key/contexto actual
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @param Closure(): mixed $resolver
|
||||||
|
* @param int|null $ttl
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function remember(string $key, Closure $resolver, ?int $ttl = null): mixed
|
||||||
|
{
|
||||||
|
return $this->getCacheManager(['key_name' => $key])->remember($resolver, $ttl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limpia el valor de caché actual según el contexto y clave
|
||||||
|
*/
|
||||||
|
public function clear(): void
|
||||||
|
{
|
||||||
|
$this->getCacheManager()->forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Devuelve el contexto actual aplicado
|
||||||
|
*/
|
||||||
|
public function getContext(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'namespace' => $this->namespace,
|
||||||
|
'component' => $this->component,
|
||||||
|
'group' => $this->group,
|
||||||
|
'section' => $this->section,
|
||||||
|
'sub_group' => $this->subGroup,
|
||||||
|
'scope' => $this->scope,
|
||||||
|
'scope_id' => $this->scope_id,
|
||||||
|
'key_name' => $this->keyName,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Devuelve la clave completa de caché calificada
|
||||||
|
*/
|
||||||
|
public function cacheKey(?string $key = null): string
|
||||||
|
{
|
||||||
|
return $this->getCacheManager(['key_name' => $key ?? $this->keyName])->qualifiedKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene el gestor de caché con el contexto aplicado
|
||||||
|
*/
|
||||||
|
protected function getCacheManager(array $overrides = []): KonekoCacheManager
|
||||||
|
{
|
||||||
|
return cache_manager([
|
||||||
|
'namespace' => $this->namespace,
|
||||||
|
'component' => $this->component,
|
||||||
|
'group' => $this->group,
|
||||||
|
'section' => $this->section,
|
||||||
|
'sub_group' => $this->subGroup,
|
||||||
|
'scope' => $this->scope,
|
||||||
|
'scope_id' => $this->scope_id,
|
||||||
|
'key_name' => $overrides['key_name'] ?? $this->keyName,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -1,191 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Cache;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\{Cache,Config,Schema};
|
|
||||||
use Koneko\VuexyAdmin\Support\Cache\AbstractKeyValueCacheBuilder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 🎛️ Servicio de gestión de variables Vuexy Admin y Customizer.
|
|
||||||
*/
|
|
||||||
class VuexyVarsBuilderService extends AbstractKeyValueCacheBuilder
|
|
||||||
{
|
|
||||||
// Namespace base
|
|
||||||
private const COMPONENT = 'core';
|
|
||||||
private const GROUP = 'layout';
|
|
||||||
|
|
||||||
//protected string $group = 'layout-builder';
|
|
||||||
|
|
||||||
// Cache scope
|
|
||||||
protected bool $isUserScoped = true;
|
|
||||||
|
|
||||||
/** @var string Settings & Cache key */
|
|
||||||
private const SETTINGS_ADMIN_VARS_KEY = 'admin-vars';
|
|
||||||
public const SETTINGS_CUSTOMIZER_VARS_KEY = 'customizer-vars';
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
parent::__construct(self::COMPONENT, self::GROUP);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtiene las variables administrativas principales.
|
|
||||||
*/
|
|
||||||
public function getAdminVars(?string $key = null): mixed
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
if (!Schema::hasTable('settings')) {
|
|
||||||
return $this->getDefaultAdminVars($key);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
$vars = $this->rememberCache(self::SETTINGS_ADMIN_VARS_KEY, function () {
|
|
||||||
$settings = settings()->setContext($this->component, $this->group)->getGroup(self::SETTINGS_ADMIN_VARS_KEY);
|
|
||||||
|
|
||||||
return $this->buildAdminVarsArray($settings);
|
|
||||||
});
|
|
||||||
|
|
||||||
return $key ? ($vars[$key] ?? null) : $vars;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtiene las configuraciones del customizador Vuexy.
|
|
||||||
*/
|
|
||||||
public function getVuexyCustomizerVars(): array
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
if (!Schema::hasTable('settings')) {
|
|
||||||
return $this->getDefaultVuexyVars();
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
return $this->rememberCache(self::SETTINGS_CUSTOMIZER_VARS_KEY, function () {
|
|
||||||
$settings = settings()->setContext($this->component, $this->group)->getGroup(self::SETTINGS_CUSTOMIZER_VARS_KEY);
|
|
||||||
|
|
||||||
return $this->buildVuexyCustomizerVars($settings);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Elimina las configuraciones del customizador Vuexy.
|
|
||||||
*/
|
|
||||||
public static function deleteVuexyCustomizerVars(): void
|
|
||||||
{
|
|
||||||
$instance = new static();
|
|
||||||
|
|
||||||
$instance->setContext($instance->component, $instance->group);
|
|
||||||
|
|
||||||
settings()->setContext($instance->component, $instance->group);
|
|
||||||
settings()->deleteGroup(self::SETTINGS_CUSTOMIZER_VARS_KEY);
|
|
||||||
|
|
||||||
Cache::forget($instance->generateCacheKey(self::SETTINGS_CUSTOMIZER_VARS_KEY));
|
|
||||||
Cache::forget(cache_manager($instance->component, $instance->group)->key(self::SETTINGS_CUSTOMIZER_VARS_KEY));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Limpia las caches del admin y del customizador Vuexy.
|
|
||||||
*/
|
|
||||||
public static function clearCache(): void
|
|
||||||
{
|
|
||||||
//Cache::forget(self::SETTINGS_ADMIN_VARS_KEY);
|
|
||||||
//Cache::forget(self::SETTINGS_CUSTOMIZER_VARS_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construye las variables del admin.
|
|
||||||
*/
|
|
||||||
private function buildAdminVarsArray(array $settings): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'title' => $settings['title'] ?? config("{$this->namespace}.title", 'Default Title'),
|
|
||||||
'author' => $settings['author'] ?? config("{$this->namespace}.author", 'Default Author'),
|
|
||||||
'description' => $settings['description'] ?? config("{$this->namespace}.description", 'Default Description'),
|
|
||||||
'favicon' => $this->buildFaviconPaths($settings),
|
|
||||||
'app_name' => $settings['app_name'] ?? config("{$this->namespace}.app_name", 'Default App Name'),
|
|
||||||
'image_logo' => $this->buildImageLogoPaths($settings),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construye las variables de Vuexy customizer.
|
|
||||||
*/
|
|
||||||
private function buildVuexyCustomizerVars(array $settings): array
|
|
||||||
{
|
|
||||||
$defaults = config("{$this->namespace}.admin.vuexy");
|
|
||||||
|
|
||||||
return collect($defaults)
|
|
||||||
->mapWithKeys(function ($defaultValue, $key) use ($settings) {
|
|
||||||
$vuexyKey = $key;
|
|
||||||
$value = $settings[$vuexyKey] ?? $defaultValue;
|
|
||||||
|
|
||||||
if (in_array($key, [
|
|
||||||
'hasCustomizer', 'displayCustomizer', 'footerFixed',
|
|
||||||
'menuFixed', 'menuCollapsed', 'showDropdownOnHover'
|
|
||||||
], true)) {
|
|
||||||
$value = filter_var($value, FILTER_VALIDATE_BOOLEAN);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [$key => $value];
|
|
||||||
})
|
|
||||||
->toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construye las rutas de favicon.
|
|
||||||
*/
|
|
||||||
private function buildFaviconPaths(array $settings): array
|
|
||||||
{
|
|
||||||
$namespace = $settings['favicon_ns'] ?? null;
|
|
||||||
$defaultFavicon = config("{$this->namespace}.favicon", 'favicon.ico');
|
|
||||||
|
|
||||||
return [
|
|
||||||
'namespace' => $namespace,
|
|
||||||
'16x16' => $namespace ? "{$namespace}_16x16.png" : $defaultFavicon,
|
|
||||||
'76x76' => $namespace ? "{$namespace}_76x76.png" : $defaultFavicon,
|
|
||||||
'120x120' => $namespace ? "{$namespace}_120x120.png" : $defaultFavicon,
|
|
||||||
'152x152' => $namespace ? "{$namespace}_152x152.png" : $defaultFavicon,
|
|
||||||
'180x180' => $namespace ? "{$namespace}_180x180.png" : $defaultFavicon,
|
|
||||||
'192x192' => $namespace ? "{$namespace}_192x192.png" : $defaultFavicon,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construye las rutas de logos.
|
|
||||||
*/
|
|
||||||
private function buildImageLogoPaths(array $settings): array
|
|
||||||
{
|
|
||||||
$defaultLogo = config("{$this->namespace}.app_logo", 'logo-default.png');
|
|
||||||
|
|
||||||
return [
|
|
||||||
'small' => $settings['image_logo_small'] ?? $defaultLogo,
|
|
||||||
'medium' => $settings['image_logo_medium'] ?? $defaultLogo,
|
|
||||||
'large' => $settings['image_logo'] ?? $defaultLogo,
|
|
||||||
'small_dark' => $settings['image_logo_small_dark'] ?? $defaultLogo,
|
|
||||||
'medium_dark' => $settings['image_logo_medium_dark'] ?? $defaultLogo,
|
|
||||||
'large_dark' => $settings['image_logo_dark'] ?? $defaultLogo,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Valores de fallback si no hay base de datos.
|
|
||||||
*/
|
|
||||||
private function getDefaultAdminVars(?string $key = null): array
|
|
||||||
{
|
|
||||||
return $key
|
|
||||||
? ($this->buildAdminVarsArray([])[$key] ?? null)
|
|
||||||
: $this->buildAdminVarsArray([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Valores de fallback para customizer Vuexy.
|
|
||||||
*/
|
|
||||||
private function getDefaultVuexyVars(): array
|
|
||||||
{
|
|
||||||
return Config::get("{$this->namespace}.admin.vuexy", []);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
17
src/Application/Config/Cast/VuexyLayoutCast.php
Normal file
17
src/Application/Config/Cast/VuexyLayoutCast.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Koneko\VuexyAdmin\Application\Config\Cast;
|
||||||
|
|
||||||
|
class VuexyLayoutCast
|
||||||
|
{
|
||||||
|
public function cast(mixed $value, string $key): mixed
|
||||||
|
{
|
||||||
|
return match (true) {
|
||||||
|
in_array($key, ['hasCustomizer', 'displayCustomizer', 'footerFixed', 'menuFixed', 'menuCollapsed', 'showDropdownOnHover']) => (bool) $value,
|
||||||
|
$key === 'maxQuickLinks' => (int) $value,
|
||||||
|
default => $value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyAdmin\Application\Config\Contracts;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Auth\Authenticatable;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
interface ConfigRepositoryInterface
|
||||||
|
{
|
||||||
|
// ==================== Factory ====================
|
||||||
|
|
||||||
|
public static function make(): static;
|
||||||
|
public static function fromArray(array $context): static;
|
||||||
|
public static function fromRequest(?Request $request = null): static;
|
||||||
|
|
||||||
|
// ==================== Context ====================
|
||||||
|
|
||||||
|
public function setNamespace(string $namespace): static;
|
||||||
|
public function setEnvironment(?string $environment = null): static;
|
||||||
|
public function setComponent(string $component): static;
|
||||||
|
|
||||||
|
public function context(string $group, ?string $section = null, ?string $subGroup = null): static;
|
||||||
|
public function setContextArray(array $context): static;
|
||||||
|
|
||||||
|
public function setScope(Model|string|false $scope, int|null|false $scopeId = false): static;
|
||||||
|
public function setScopeId(?int $scopeId): static;
|
||||||
|
public function setUser(Authenticatable|int|null|false $user): static;
|
||||||
|
public function withScopeFromModel(Model $model): static;
|
||||||
|
|
||||||
|
public function setGroup(string $group): static;
|
||||||
|
public function setSection(string $section): static;
|
||||||
|
public function setSubGroup(string $subGroup): static;
|
||||||
|
public function setKeyName(string $keyName): static;
|
||||||
|
|
||||||
|
// ==================== Getters ====================
|
||||||
|
|
||||||
|
public function get(string $qualifiedKey, mixed $default = null): mixed;
|
||||||
|
public function fromDb(bool $fromDb = true): static;
|
||||||
|
public function sourceOf(string $qualifiedKeySufix): ?string;
|
||||||
|
|
||||||
|
public function qualifiedKey(?string $key = null): string;
|
||||||
|
public function getScopeModel(): ?Model;
|
||||||
|
|
||||||
|
// ==================== Advanced ====================
|
||||||
|
|
||||||
|
public function syncFromRegistry(string $configKey, bool $forceReload = false): static;
|
||||||
|
|
||||||
|
/*
|
||||||
|
public function loadAll(): array;
|
||||||
|
public function loadByContext(): array;
|
||||||
|
public function rememberConfig(Closure $callback): mixed;
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ==================== Utils ====================
|
||||||
|
|
||||||
|
public function has(string $qualifiedKey): bool;
|
||||||
|
//public function hasContext(): bool;
|
||||||
|
public function reset(): static;
|
||||||
|
|
||||||
|
// ==================== Diagnostics ====================
|
||||||
|
|
||||||
|
public function info(): array;
|
||||||
|
}
|
62
src/Application/Config/Manager/Concerns/HasConfigContext.php
Normal file
62
src/Application/Config/Manager/Concerns/HasConfigContext.php
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyAdmin\Application\Config\Manager\Concerns;
|
||||||
|
|
||||||
|
use Koneko\VuexyAdmin\Application\Traits\System\Context\HasBaseContext;
|
||||||
|
use Koneko\VuexyAdmin\Application\Traits\System\Context\HasConfigContextValidation;
|
||||||
|
|
||||||
|
trait __HasConfigContext
|
||||||
|
{
|
||||||
|
use HasBaseContext;
|
||||||
|
use HasConfigContextValidation;
|
||||||
|
|
||||||
|
protected function validateKeyName(string $keyName): string
|
||||||
|
{
|
||||||
|
if (!preg_match('/^[a-zA-Z0-9]+$/', $keyName)) {
|
||||||
|
throw new \InvalidArgumentException("El valor '{$keyName}' de 'keyName' debe ser un slug válido.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen($keyName) > 64) {
|
||||||
|
throw new \InvalidArgumentException("El valor de 'keyName' excede 64 caracteres.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $keyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================= GETTERS =========================
|
||||||
|
|
||||||
|
public function qualifiedKey(?string $key = null): string
|
||||||
|
{
|
||||||
|
$parts = [
|
||||||
|
$this->context['namespace'],
|
||||||
|
$this->context['component'],
|
||||||
|
$this->context['group'],
|
||||||
|
$this->context['section'],
|
||||||
|
$this->context['sub_group'],
|
||||||
|
$key ?? $this->context['key_name'],
|
||||||
|
];
|
||||||
|
|
||||||
|
return collect($parts)
|
||||||
|
->filter()
|
||||||
|
->implode('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function qualifiedKeyPrefix(): string
|
||||||
|
{
|
||||||
|
$parts = [
|
||||||
|
$this->context['namespace'],
|
||||||
|
$this->context['component'],
|
||||||
|
];
|
||||||
|
|
||||||
|
return collect($parts)->filter()->implode('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================= HELPERS =========================
|
||||||
|
|
||||||
|
public function ensureQualifiedKey(): void
|
||||||
|
{
|
||||||
|
if (!$this->hasBaseContext()) {
|
||||||
|
throw new \InvalidArgumentException("Falta definir el contexto base y 'key_name' en config().");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
257
src/Application/Config/Manager/KonekoConfigManager.php
Normal file
257
src/Application/Config/Manager/KonekoConfigManager.php
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyAdmin\Application\Config\Manager;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\{Auth, Config};
|
||||||
|
use Koneko\VuexyAdmin\Application\Config\Registry\ConfigBlockRegistry;
|
||||||
|
use Koneko\VuexyAdmin\Application\Config\Contracts\ConfigRepositoryInterface;
|
||||||
|
use Koneko\VuexyAdmin\Application\CoreModule;
|
||||||
|
use Koneko\VuexyAdmin\Application\Traits\System\Context\{HasBaseContext, HasConfigContextValidation};
|
||||||
|
|
||||||
|
final class KonekoConfigManager implements ConfigRepositoryInterface
|
||||||
|
{
|
||||||
|
use HasBaseContext;
|
||||||
|
use HasConfigContextValidation;
|
||||||
|
|
||||||
|
public bool $fromDb = false;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->setNamespace(CoreModule::NAMESPACE)
|
||||||
|
->setEnvironment()
|
||||||
|
->setComponent(CoreModule::COMPONENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Factory ====================
|
||||||
|
|
||||||
|
public static function make(): static
|
||||||
|
{
|
||||||
|
return new static();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================= 🔍 LECTURA =========================
|
||||||
|
|
||||||
|
public function get(?string $keyName = null, mixed $default = null): mixed
|
||||||
|
{
|
||||||
|
$this->setKeyName($keyName ?? $this->context['key_name']);
|
||||||
|
|
||||||
|
// Resolvemos el qualified key
|
||||||
|
$qualifiedKey = $this->qualifiedKey();
|
||||||
|
|
||||||
|
// Si directo, obtenemos el valor directamente de config
|
||||||
|
if (!$this->fromDb) {
|
||||||
|
return config($qualifiedKey, $default);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prioridad 1: override desde settings
|
||||||
|
$value = settings()
|
||||||
|
->setContextArray($this->context)
|
||||||
|
->get($this->context['key_name']);
|
||||||
|
|
||||||
|
// Prioridad 2: valor directo de config
|
||||||
|
return $value ?? config($qualifiedKey, $default);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fromDb(bool $fromDb = true): static
|
||||||
|
{
|
||||||
|
$this->fromDb = $fromDb;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function has(string $key): bool
|
||||||
|
{
|
||||||
|
$this->setKeyName($key);
|
||||||
|
return settings()->setContextArray($this->context)->exists($key)
|
||||||
|
|| config()->has($this->qualifiedKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sourceOf(?string $key = null): string
|
||||||
|
{
|
||||||
|
$this->setKeyName($key ?? $this->context['key_name']);
|
||||||
|
$qualifiedKey = $this->qualifiedKey();
|
||||||
|
|
||||||
|
// Prioridad 1: override desde settings
|
||||||
|
if (settings()->setContextArray($this->context)->exists($this->context['key_name'])) {
|
||||||
|
return 'database';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prioridad 2: valor directo de config
|
||||||
|
if (config()->has($qualifiedKey)) {
|
||||||
|
return 'config';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'default';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function info(): array
|
||||||
|
{
|
||||||
|
$qualified = $this->qualifiedKey();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'qualified_key' => $qualified,
|
||||||
|
'context' => $this->context,
|
||||||
|
'value' => $this->get(),
|
||||||
|
'source' => $this->sourceOf(),
|
||||||
|
'has_config' => config()->has($qualified),
|
||||||
|
'has_db' => settings()->setContextArray($this->context)->exists($this->context['key_name']),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================= HELPERS =========================
|
||||||
|
|
||||||
|
protected function validateSlug(string $field, string $value, int $maxLength): string
|
||||||
|
{
|
||||||
|
if (!preg_match('/^[a-zA-Z0-9_\-]+$/', $value)) {
|
||||||
|
throw new \InvalidArgumentException("El valor '{$value}' de '{$field}' debe ser un slug válido.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen($value) > $maxLength) {
|
||||||
|
throw new \InvalidArgumentException("El valor de '{$field}' excede {$maxLength} caracteres.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function validateKeyName(string $keyName): string
|
||||||
|
{
|
||||||
|
if (!preg_match('/^[a-zA-Z0-9-._]+$/', $keyName)) {
|
||||||
|
throw new \InvalidArgumentException("El valor '{$keyName}' de 'keyName' debe ser un slug válido.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen($keyName) > 64) {
|
||||||
|
throw new \InvalidArgumentException("El valor de 'keyName' excede 64 caracteres.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $keyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ensureQualifiedKey(): void
|
||||||
|
{
|
||||||
|
if (!$this->hasBaseContext()) {
|
||||||
|
throw new \InvalidArgumentException("Falta definir el contexto base y 'key_name' en config().");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================= GETTERS =========================
|
||||||
|
|
||||||
|
public function qualifiedKey(?string $key = null): string
|
||||||
|
{
|
||||||
|
$parts = [
|
||||||
|
$this->context['namespace'],
|
||||||
|
$this->context['component'],
|
||||||
|
$this->context['group'],
|
||||||
|
$this->context['section'],
|
||||||
|
$this->context['sub_group'],
|
||||||
|
$key ?? $this->context['key_name'],
|
||||||
|
];
|
||||||
|
|
||||||
|
return collect($parts)
|
||||||
|
->filter()
|
||||||
|
->implode('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function qualifiedKeyPrefix(): string
|
||||||
|
{
|
||||||
|
$parts = [
|
||||||
|
$this->context['namespace'],
|
||||||
|
$this->context['component'],
|
||||||
|
];
|
||||||
|
|
||||||
|
return collect($parts)->filter()->implode('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================= HELPERS =========================
|
||||||
|
|
||||||
|
public function reset(): static
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================= Config Blocks =========================
|
||||||
|
|
||||||
|
public function syncFromRegistry(string $configKey, bool $forceReload = false): static
|
||||||
|
{
|
||||||
|
$config = ConfigBlockRegistry::get($configKey);
|
||||||
|
|
||||||
|
$manager = cache_m()
|
||||||
|
->setComponent($config['component'])
|
||||||
|
->context($config['group'], $config['section'], $config['sub_group'])
|
||||||
|
->setUser(Auth::user())
|
||||||
|
->setKeyName($config['key_name']);
|
||||||
|
|
||||||
|
if ($forceReload) {
|
||||||
|
$manager->forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
$castFn = isset($config['cast']) && class_exists($config['cast'])
|
||||||
|
? [app($config['cast']), 'cast']
|
||||||
|
: fn ($v, $k) => $v;
|
||||||
|
|
||||||
|
if (!$manager->isEnabled()) {
|
||||||
|
// Bypass de cache: usamos el callback sin guardar en Redis
|
||||||
|
$castFn = isset($config['cast']) && class_exists($config['cast'])
|
||||||
|
? [app($config['cast']), 'cast']
|
||||||
|
: fn ($v, $k) => $v;
|
||||||
|
|
||||||
|
$base = config($configKey, []);
|
||||||
|
$settings = settings()
|
||||||
|
->setComponent($config['component'])
|
||||||
|
->context($config['group'], $config['section'], $config['sub_group'])
|
||||||
|
->setUser(Auth::user())
|
||||||
|
->getSubGroup(true);
|
||||||
|
|
||||||
|
$merged = array_replace_recursive($base, array_map($castFn, $settings, array_keys($settings)));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Cache activada, usamos remember
|
||||||
|
$merged = $manager->rememberWithTTLResolution(function () use ($configKey, $config, $castFn) {
|
||||||
|
$base = config($configKey, []);
|
||||||
|
$settings = settings()
|
||||||
|
->setComponent($config['component'])
|
||||||
|
->context($config['group'], $config['section'], $config['sub_group'])
|
||||||
|
->setUser(Auth::user())
|
||||||
|
->getSubGroup(true);
|
||||||
|
|
||||||
|
return array_replace_recursive($base, array_map($castFn, $settings, array_keys($settings)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Config::set($configKey, $merged);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================= ESCRITURA =========================
|
||||||
|
|
||||||
|
public function set(mixed $value, ?string $keyName = null): void
|
||||||
|
{
|
||||||
|
$this->setKeyName($keyName ?? $this->context['key_name']);
|
||||||
|
$qualified = $this->qualifiedKey();
|
||||||
|
|
||||||
|
// Seguridad: solo sobrescribir valores existentes en config
|
||||||
|
if (!config()->has($qualified)) {
|
||||||
|
throw new \LogicException("❌ No se puede sobrescribir '{$qualified}' porque no existe en archivo de configuración.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seguridad: si ya hay un setting y no es de tipo config
|
||||||
|
$existing = settings()
|
||||||
|
->setContextArray($this->context)
|
||||||
|
->get($this->context['key_name']);
|
||||||
|
|
||||||
|
if ($existing && !($existing->is_config ?? false)) {
|
||||||
|
throw new \LogicException("⚠️ El setting '{$qualified}' ya existe en DB pero no está marcado como 'is_config'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escritura segura con flag `is_config = true`
|
||||||
|
settings()
|
||||||
|
->setContextArray($this->context)
|
||||||
|
->markAsSystem(true)
|
||||||
|
->markAsActive(true)
|
||||||
|
->setDescription("Override del archivo de configuración '{$qualified}'")
|
||||||
|
->setHint("Este valor reemplaza el valor original definido en config/")
|
||||||
|
->setInternalConfigFlag()
|
||||||
|
->set($value);
|
||||||
|
}
|
||||||
|
}
|
149
src/Application/Config/Manager/___KonekoConfigManager copy.php
Normal file
149
src/Application/Config/Manager/___KonekoConfigManager copy.php
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyAdmin\Application\Config\Manager;
|
||||||
|
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Koneko\VuexyAdmin\Application\Bootstrap\Registry\KonekoModuleRegistry;
|
||||||
|
use Koneko\VuexyAdmin\Application\Settings\Manager\KonekoSettingManager;
|
||||||
|
|
||||||
|
final class ___KonekoConfigManager
|
||||||
|
{
|
||||||
|
protected string $namespace;
|
||||||
|
protected string $component;
|
||||||
|
protected string $group;
|
||||||
|
protected string $section = 'config';
|
||||||
|
protected string $subGroup = 'default';
|
||||||
|
protected ?string $scope = null;
|
||||||
|
protected ?string $scope_id = null;
|
||||||
|
protected ?string $key = null;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
protected KonekoSettingManager $settings
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function from(string|object $module, string $qualifiedGroup): static
|
||||||
|
{
|
||||||
|
[$this->namespace, $this->component] = $this->resolveModuleParts($module);
|
||||||
|
|
||||||
|
$parts = explode('.', $qualifiedGroup);
|
||||||
|
$this->group = $parts[0] ?? 'general';
|
||||||
|
$this->subGroup = $parts[1] ?? 'default';
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopedToUser(int|string $userId): static
|
||||||
|
{
|
||||||
|
$this->scope = 'user';
|
||||||
|
$this->scope_id = (string) $userId;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSection(string $section): static
|
||||||
|
{
|
||||||
|
$this->section = $section;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(string $key, mixed $default = null): mixed
|
||||||
|
{
|
||||||
|
$this->key = $key;
|
||||||
|
|
||||||
|
// Paso 1: SETTINGS DB
|
||||||
|
$value = $this->settings
|
||||||
|
->setNamespace($this->namespace)
|
||||||
|
->setComponent($this->component)
|
||||||
|
->setGroup($this->group)
|
||||||
|
->setSection($this->section)
|
||||||
|
->setSubGroup($this->subGroup)
|
||||||
|
->setKeyName($this->key);
|
||||||
|
|
||||||
|
if ($this->scope && $this->scope_id) {
|
||||||
|
$value->setScope($this->scope, $this->scope_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $value->get();
|
||||||
|
if (!is_null($result)) return $result;
|
||||||
|
|
||||||
|
// Paso 2: ENV
|
||||||
|
$envKey = strtoupper(str_replace('.', '_', $this->qualifiedKey()));
|
||||||
|
if (env($envKey) !== null) return env($envKey);
|
||||||
|
|
||||||
|
// Paso 3: CONFIG LOCAL (config/ publicado)
|
||||||
|
$configValue = config($this->qualifiedKey());
|
||||||
|
if (!is_null($configValue)) return $configValue;
|
||||||
|
|
||||||
|
// Paso 4: CONFIG DEL MÓDULO (registrado en tiempo de boot)
|
||||||
|
if (KonekoModuleRegistry::has($this->component)) {
|
||||||
|
$moduleConfig = config("{$this->namespace}.{$this->component}");
|
||||||
|
return Arr::get($moduleConfig, $this->nestedKey(), $default);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sourceOf(string $key): ?string
|
||||||
|
{
|
||||||
|
$this->key = $key;
|
||||||
|
|
||||||
|
$checker = $this->settings
|
||||||
|
->setNamespace($this->namespace)
|
||||||
|
->setComponent($this->component)
|
||||||
|
->setGroup($this->group)
|
||||||
|
->setSection($this->section)
|
||||||
|
->setSubGroup($this->subGroup);
|
||||||
|
|
||||||
|
if ($this->scope && $this->scope_id) {
|
||||||
|
$checker->setScope($this->scope, $this->scope_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($checker->exists($key)) return 'settings';
|
||||||
|
|
||||||
|
$envKey = strtoupper(str_replace('.', '_', $this->qualifiedKey()));
|
||||||
|
if (env($envKey) !== null) return 'env';
|
||||||
|
|
||||||
|
if (!is_null(config($this->qualifiedKey()))) return 'config';
|
||||||
|
|
||||||
|
if (KonekoModuleRegistry::has($this->component)) return 'module';
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function resolveModuleParts(string|object $module): array
|
||||||
|
{
|
||||||
|
if (is_object($module)) {
|
||||||
|
$module = get_class($module);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (class_exists($module) && defined("$module::NAMESPACE") && defined("$module::COMPONENT")) {
|
||||||
|
return [constant("$module::NAMESPACE"), constant("$module::COMPONENT")];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Str::contains($module, '.')) {
|
||||||
|
return explode('.', $module, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \InvalidArgumentException("No se pudo resolver el módulo desde: {$module}");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function qualifiedKey(): string
|
||||||
|
{
|
||||||
|
return implode('.', [
|
||||||
|
$this->namespace,
|
||||||
|
$this->component,
|
||||||
|
$this->group,
|
||||||
|
$this->subGroup,
|
||||||
|
$this->key
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function nestedKey(): string
|
||||||
|
{
|
||||||
|
return implode('.', [
|
||||||
|
$this->group,
|
||||||
|
$this->subGroup,
|
||||||
|
$this->key
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
37
src/Application/Config/Registry/ConfigBlockRegistry.php
Normal file
37
src/Application/Config/Registry/ConfigBlockRegistry.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyAdmin\Application\Config\Registry;
|
||||||
|
|
||||||
|
final class ConfigBlockRegistry
|
||||||
|
{
|
||||||
|
protected static array $blocks = [];
|
||||||
|
|
||||||
|
public static function register(string $key, array $config): void
|
||||||
|
{
|
||||||
|
static::$blocks[$key] = $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function get(string $key): array
|
||||||
|
{
|
||||||
|
if (!static::exists($key)) {
|
||||||
|
throw new \InvalidArgumentException("Bloque '$key' no registrado.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return static::$blocks[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function exists(string $key): bool
|
||||||
|
{
|
||||||
|
return isset(static::$blocks[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function all(): array
|
||||||
|
{
|
||||||
|
return static::$blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function clear(): void
|
||||||
|
{
|
||||||
|
static::$blocks = [];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,146 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyAdmin\Application\Config\Builder;
|
||||||
|
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Contracts\Auth\Authenticatable;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Koneko\VuexyAdmin\Application\Settings\KonekoSettingManager;
|
||||||
|
use Koneko\VuexyAdmin\Application\Bootstrap\Registry\KonekoModuleRegistry;
|
||||||
|
|
||||||
|
class KonekoConfigResolverService
|
||||||
|
{
|
||||||
|
protected KonekoSettingManager $settings;
|
||||||
|
|
||||||
|
public function __construct(KonekoSettingManager $settings)
|
||||||
|
{
|
||||||
|
$this->settings = $settings;
|
||||||
|
|
||||||
|
$this
|
||||||
|
->setNamespace(CoreModule::NAMESPACE)
|
||||||
|
->setEnvironment();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene una clave con jerarquía: settings > .env > config local > config de módulo.
|
||||||
|
*/
|
||||||
|
public function get(string $qualifiedKey, mixed $default = null): mixed
|
||||||
|
{
|
||||||
|
// Extraer segmentos para contexto (namespace.component.group.section.subgroup.key)
|
||||||
|
$parts = explode('.', $qualifiedKey);
|
||||||
|
|
||||||
|
if (count($parts) < 3) {
|
||||||
|
return config($qualifiedKey, $default); // fallback si es un config no calificado
|
||||||
|
}
|
||||||
|
|
||||||
|
$namespace = $parts[0];
|
||||||
|
$component = $parts[1];
|
||||||
|
$group = $parts[2] ?? 'general';
|
||||||
|
$section = 'config';
|
||||||
|
$subGroup = $parts[3] ?? 'default';
|
||||||
|
$key = $parts[4] ?? end($parts);
|
||||||
|
|
||||||
|
// 1. SETTINGS (BD, sección config)
|
||||||
|
$value = $this->settings
|
||||||
|
->setNamespace($namespace)
|
||||||
|
->setComponent($component)
|
||||||
|
->setGroup($group)
|
||||||
|
->setSection($section)
|
||||||
|
->setSubGroup($subGroup)
|
||||||
|
->setKeyName($key)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
if (!is_null($value)) return $value;
|
||||||
|
|
||||||
|
// 2. ENV (si existe como override)
|
||||||
|
$envKey = strtoupper(str_replace('.', '_', $qualifiedKey));
|
||||||
|
if (env($envKey) !== null) {
|
||||||
|
return env($envKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. CONFIG LOCAL (config/koneko/{component}.php)
|
||||||
|
$value = config($qualifiedKey);
|
||||||
|
if (!is_null($value)) return $value;
|
||||||
|
|
||||||
|
// 4. CONFIG DEL MÓDULO (registrado por orquestador)
|
||||||
|
$moduleKey = "$namespace.$component";
|
||||||
|
if ($module = KonekoModuleRegistry::get($component)) {
|
||||||
|
$moduleConfig = config($moduleKey);
|
||||||
|
$nestedKey = implode('.', array_slice($parts, 2));
|
||||||
|
return Arr::get($moduleConfig, $nestedKey, $default);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resuelve una clave usando una clase declarativa del módulo como contexto.
|
||||||
|
*
|
||||||
|
* @param string $moduleClass Ej: CoreModule::class
|
||||||
|
* @param string $qualifiedKey Ej: 'core.menu.cache.ttl'
|
||||||
|
* @param mixed $default
|
||||||
|
*/
|
||||||
|
public function fromModuleClass(string $moduleClass, string $qualifiedKey, mixed $default = null): mixed
|
||||||
|
{
|
||||||
|
if (!class_exists($moduleClass)) {
|
||||||
|
throw new \InvalidArgumentException("Clase de módulo no encontrada: {$moduleClass}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!defined("$moduleClass::NAMESPACE") || !defined("$moduleClass::COMPONENT")) {
|
||||||
|
throw new \InvalidArgumentException("La clase de módulo debe definir las constantes NAMESPACE y COMPONENT.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$namespace = constant("$moduleClass::NAMESPACE");
|
||||||
|
$component = constant("$moduleClass::COMPONENT");
|
||||||
|
|
||||||
|
// Prefijar si aún no lo está (ej: 'core.menu.cache.ttl' -> 'koneko.core.menu.cache.ttl')
|
||||||
|
if (!str_starts_with($qualifiedKey, "$namespace.")) {
|
||||||
|
$qualifiedKey = "$namespace.$qualifiedKey";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->get($qualifiedKey, $default);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Devuelve la fuente de la clave (para inspección o debugging).
|
||||||
|
*/
|
||||||
|
public function sourceOf(string $qualifiedKey): ?string
|
||||||
|
{
|
||||||
|
$parts = explode('.', $qualifiedKey);
|
||||||
|
|
||||||
|
if (count($parts) < 3) return null;
|
||||||
|
|
||||||
|
$namespace = $parts[0];
|
||||||
|
$component = $parts[1];
|
||||||
|
$group = $parts[2] ?? 'general';
|
||||||
|
$section = 'config';
|
||||||
|
$subGroup = $parts[3] ?? 'default';
|
||||||
|
$key = $parts[4] ?? end($parts);
|
||||||
|
|
||||||
|
$has = $this->settings
|
||||||
|
->setNamespace($namespace)
|
||||||
|
->setComponent($component)
|
||||||
|
->setGroup($group)
|
||||||
|
->setSection($section)
|
||||||
|
->setSubGroup($subGroup)
|
||||||
|
->exists($key);
|
||||||
|
|
||||||
|
if ($has) return 'settings';
|
||||||
|
|
||||||
|
$envKey = strtoupper(str_replace('.', '_', $qualifiedKey));
|
||||||
|
if (env($envKey) !== null) {
|
||||||
|
return 'env';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_null(config($qualifiedKey))) {
|
||||||
|
return 'config';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (KonekoModuleRegistry::has($component)) {
|
||||||
|
return 'module';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -1,32 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Contracts\ApiRegistry;
|
|
||||||
|
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Koneko\VuexyAdmin\Models\ExternalApi;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contrato para servicios de registro de APIs externas.
|
|
||||||
*/
|
|
||||||
interface ExternalApiRegistryInterface
|
|
||||||
{
|
|
||||||
public function all(): Collection;
|
|
||||||
|
|
||||||
public function active(): Collection;
|
|
||||||
|
|
||||||
public function groupByProvider(): Collection;
|
|
||||||
|
|
||||||
public function groupByModule(): Collection;
|
|
||||||
|
|
||||||
public function forModule(string $module): Collection;
|
|
||||||
|
|
||||||
public function forProvider(string $provider): Collection;
|
|
||||||
|
|
||||||
public function summary(): array;
|
|
||||||
|
|
||||||
public function slugifyName(string $name): string;
|
|
||||||
|
|
||||||
public function find(string $slug): ?ExternalApi;
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Contracts\Catalogs;
|
|
||||||
|
|
||||||
interface CatalogServiceInterface
|
|
||||||
{
|
|
||||||
public function catalogs(): array;
|
|
||||||
public function exists(string $catalog): bool;
|
|
||||||
public function getCatalog(string $catalog, string $searchTerm = '', array $options = []): array;
|
|
||||||
public function getCatalogMeta(string $catalog): array;
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Contracts\Factories;
|
|
||||||
|
|
||||||
interface ProbabilisticAttributesFactoryInterface
|
|
||||||
{
|
|
||||||
public function maybe(int $percentage, mixed $value): mixed;
|
|
||||||
public function maybeDefault(mixed $value): mixed;
|
|
||||||
}
|
|
@ -1,89 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Contracts\Modules;
|
|
||||||
|
|
||||||
use Koneko\VuexyAdmin\Application\Bootstrap\KonekoModule;
|
|
||||||
|
|
||||||
interface KonekoModuleServiceInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Lista los módulos instalados en el sistema.
|
|
||||||
*
|
|
||||||
* @return KonekoModule[]
|
|
||||||
*/
|
|
||||||
public function listInstalled(): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lista los módulos disponibles en el marketplace oficial.
|
|
||||||
*
|
|
||||||
* @return array Módulos con metadatos
|
|
||||||
*/
|
|
||||||
public function listAvailable(): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtiene un módulo instalado por su slug.
|
|
||||||
*/
|
|
||||||
public function get(string $slug): ?KonekoModule;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instala un módulo oficial desde el marketplace.
|
|
||||||
*/
|
|
||||||
public function installFromMarketplace(string $slug): bool;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instala un módulo desde una URL externa (ej. GitHub, repositorio privado).
|
|
||||||
*/
|
|
||||||
public function installFromUrl(string $url): bool;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Activa un módulo previamente instalado.
|
|
||||||
*/
|
|
||||||
public function enable(string $slug): bool;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Desactiva un módulo instalado (sin eliminarlo físicamente).
|
|
||||||
*/
|
|
||||||
public function disable(string $slug): bool;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Desinstala un módulo: elimina archivos y entrada en base de datos.
|
|
||||||
*/
|
|
||||||
public function uninstall(string $slug): bool;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ejecuta migraciones de un módulo.
|
|
||||||
*/
|
|
||||||
public function migrate(string $slug): bool;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reversión de migraciones del módulo (rollback).
|
|
||||||
*/
|
|
||||||
public function rollback(string $slug): bool;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Publica los assets del módulo.
|
|
||||||
*/
|
|
||||||
public function publishAssets(string $slug): bool;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Elimina los assets previamente publicados.
|
|
||||||
*/
|
|
||||||
public function removeAssets(string $slug): bool;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sincroniza los módulos detectados en el filesystem con los registrados en la DB.
|
|
||||||
*/
|
|
||||||
public function syncFromModules(): bool;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lista todos los paquetes composer (instalados) que podrían ser módulos.
|
|
||||||
*/
|
|
||||||
public function detectAllComposerModules(): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtiene metadatos extendidos de un módulo (instalado o no).
|
|
||||||
*/
|
|
||||||
public function getExtendedDetails(string $slug): array;
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Contracts\Settings;
|
|
||||||
|
|
||||||
use Koneko\VuexyAdmin\Models\Setting;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contrato para los servicios de gestión de Settings modulares.
|
|
||||||
* Proporciona una API fluida para acceder y modificar configuraciones de manera modular.
|
|
||||||
*/
|
|
||||||
interface SettingsRepositoryInterface
|
|
||||||
{
|
|
||||||
public function get(string $key, ...$args): mixed;
|
|
||||||
|
|
||||||
public function set(string $key, mixed $value, ?int $userId = null, ...$args): ?Setting;
|
|
||||||
|
|
||||||
public function delete(string $key, ?int $userId = null): bool;
|
|
||||||
|
|
||||||
public function exists(string $key): bool;
|
|
||||||
|
|
||||||
public function getGroup(string $group, ?int $userId = null): array;
|
|
||||||
|
|
||||||
public function getComponent(string $component, ?int $userId = null): array;
|
|
||||||
|
|
||||||
public function deleteGroup(string $group, ?int $userId = null): int;
|
|
||||||
|
|
||||||
public function deleteComponent(string $component, ?int $userId = null): int;
|
|
||||||
|
|
||||||
public function listGroups(): array;
|
|
||||||
|
|
||||||
public function listComponents(): array;
|
|
||||||
|
|
||||||
public function currentNamespace(): string;
|
|
||||||
|
|
||||||
public function currentGroup(): string;
|
|
||||||
|
|
||||||
public function setContext(string $component, string $group): static;
|
|
||||||
|
|
||||||
public function setComponent(string $component): static;
|
|
||||||
|
|
||||||
public function setGroup(string $group): static;
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Contracts\Settings;
|
|
||||||
|
|
||||||
use Koneko\VuexyAdmin\Models\Setting;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contrato para los servicios de gestión de Settings modulares.
|
|
||||||
* Proporciona una API fluida para acceder y modificar configuraciones de manera modular.
|
|
||||||
*/
|
|
||||||
interface SettingsRepositoryInterface
|
|
||||||
{
|
|
||||||
public function set(string $key, mixed $value): ?Setting;
|
|
||||||
|
|
||||||
public function get(string $key, mixed $default = null): mixed;
|
|
||||||
|
|
||||||
public function delete(string $key): bool;
|
|
||||||
|
|
||||||
public function exists(string $key): bool;
|
|
||||||
|
|
||||||
|
|
||||||
public function markAsSystem(bool $state = true): static;
|
|
||||||
public function markAsEncrypted(bool $state = true): static;
|
|
||||||
public function markAsSensitive(bool $state = true): static;
|
|
||||||
public function markAsEditable(bool $state = true): static;
|
|
||||||
public function markAsActive(bool $state = true): static;
|
|
||||||
|
|
||||||
|
|
||||||
public function withoutUsageTracking(): static;
|
|
||||||
}
|
|
9
src/Application/CoreModule.php
Normal file
9
src/Application/CoreModule.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyAdmin\Application;
|
||||||
|
|
||||||
|
final class CoreModule
|
||||||
|
{
|
||||||
|
public const NAMESPACE = 'koneko';
|
||||||
|
public const COMPONENT = 'core';
|
||||||
|
}
|
@ -1,31 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Enums\ExternalApi;
|
|
||||||
|
|
||||||
enum ApiProvider: string
|
|
||||||
{
|
|
||||||
case Google = 'google';
|
|
||||||
case Banxico = 'banxico';
|
|
||||||
case SAT = 'sat';
|
|
||||||
case Custom = 'custom';
|
|
||||||
case Esys = 'esys';
|
|
||||||
case Facebook = 'facebook';
|
|
||||||
case Twitter = 'twitter';
|
|
||||||
case TawkTo = 'tawk_to';
|
|
||||||
|
|
||||||
public function label(): string
|
|
||||||
{
|
|
||||||
return match ($this) {
|
|
||||||
self::Google => 'Google',
|
|
||||||
self::Banxico => 'Banxico',
|
|
||||||
self::SAT => 'SAT (México)',
|
|
||||||
self::Custom => 'Personalizado',
|
|
||||||
self::Esys => 'ESYS',
|
|
||||||
self::Facebook => 'Facebook',
|
|
||||||
self::Twitter => 'Twitter',
|
|
||||||
self::TawkTo => 'Tawk.to',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Enums\Settings;
|
|
||||||
|
|
||||||
use Koneko\VuexyAdmin\Support\Traits\Enums\HasEnumHelpers;
|
|
||||||
|
|
||||||
enum SettingEnvironment: string
|
|
||||||
{
|
|
||||||
use HasEnumHelpers;
|
|
||||||
|
|
||||||
case PROD = 'prod';
|
|
||||||
case DEV = 'dev';
|
|
||||||
case STAGING = 'staging';
|
|
||||||
case TEST = 'test';
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Enums\Settings;
|
|
||||||
|
|
||||||
use Koneko\VuexyAdmin\Support\Traits\Enums\HasEnumHelpers;
|
|
||||||
|
|
||||||
enum SettingScope: string
|
|
||||||
{
|
|
||||||
use HasEnumHelpers;
|
|
||||||
|
|
||||||
case GLOBAL = 'global';
|
|
||||||
case TENANT = 'tenant';
|
|
||||||
case BRANCH = 'branch';
|
|
||||||
case USER = 'user';
|
|
||||||
case GUEST = 'guest';
|
|
||||||
}
|
|
36
src/Application/Events/Notifications/NotificationEmitted.php
Normal file
36
src/Application/Events/Notifications/NotificationEmitted.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Koneko\VuexyAdmin\Application\Events\Notifications;
|
||||||
|
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class NotificationEmitted implements ShouldBroadcast
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
public readonly object $notification
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the channels the event should broadcast on.
|
||||||
|
*
|
||||||
|
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||||
|
*/
|
||||||
|
public function broadcastOn(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new PrivateChannel('notifications'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -1,19 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Events\Settings;
|
|
||||||
|
|
||||||
use Illuminate\Foundation\Events\Dispatchable;
|
|
||||||
|
|
||||||
class SettingChanged
|
|
||||||
{
|
|
||||||
use Dispatchable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $key Clave completa del setting (ej. 'koneko.admin.site.logo')
|
|
||||||
* @param string $namespace Namespace base (ej. 'koneko.admin.site.')
|
|
||||||
* @param int|null $userId Usuario relacionado si es setting scoped, null si es global
|
|
||||||
*/
|
|
||||||
public function __construct(public string $key) {}
|
|
||||||
}
|
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Support\Helpers;
|
namespace Koneko\VuexyAdmin\Application\Helpers;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
|
@ -6,14 +6,13 @@ namespace Koneko\VuexyAdmin\Application\Helpers;
|
|||||||
|
|
||||||
use Illuminate\Support\Facades\Config;
|
use Illuminate\Support\Facades\Config;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use Koneko\VuexyAdmin\Application\CoreModule;
|
||||||
|
|
||||||
class VuexyHelper
|
class VuexyHelper
|
||||||
{
|
{
|
||||||
public const NAMESPACE = 'koneko';
|
|
||||||
|
|
||||||
public static function appClasses()
|
public static function appClasses()
|
||||||
{
|
{
|
||||||
$data = config('koneko.admin.vuexy');
|
$data = config_m()->get('layout.vuexy', []);
|
||||||
|
|
||||||
// default data array
|
// default data array
|
||||||
$DefaultData = [
|
$DefaultData = [
|
||||||
@ -203,8 +202,10 @@ class VuexyHelper
|
|||||||
{
|
{
|
||||||
if (isset($pageConfigs)) {
|
if (isset($pageConfigs)) {
|
||||||
if (count($pageConfigs) > 0) {
|
if (count($pageConfigs) > 0) {
|
||||||
|
$config_path = CoreModule::NAMESPACE . '.' . CoreModule::COMPONENT . '.layout.vuexy.';
|
||||||
|
|
||||||
foreach ($pageConfigs as $config => $val) {
|
foreach ($pageConfigs as $config => $val) {
|
||||||
Config::set('koneko.admin.vuexy.' . $config, $val);
|
Config::set($config_path . $config, $val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,8 @@ namespace Koneko\VuexyAdmin\Application\Http\Controllers;
|
|||||||
|
|
||||||
use Illuminate\Routing\Controller;
|
use Illuminate\Routing\Controller;
|
||||||
use Illuminate\Http\{JsonResponse, Request};
|
use Illuminate\Http\{JsonResponse, Request};
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Koneko\VuexyAdmin\Application\CoreModule;
|
||||||
use Koneko\VuexyAdmin\Application\UX\Navbar\{VuexyQuicklinksBuilderService,VuexySearchBarBuilderService};
|
use Koneko\VuexyAdmin\Application\UX\Navbar\VuexySearchBarBuilder;
|
||||||
|
|
||||||
class VuexyNavbarController extends Controller
|
class VuexyNavbarController extends Controller
|
||||||
{
|
{
|
||||||
@ -21,7 +21,7 @@ class VuexyNavbarController extends Controller
|
|||||||
{
|
{
|
||||||
abort_if(!request()->expectsJson(), 403, __('errors.ajax_only'));
|
abort_if(!request()->expectsJson(), 403, __('errors.ajax_only'));
|
||||||
|
|
||||||
return response()->json(app(VuexySearchBarBuilderService::class)->getSearchData());
|
return response()->json(app(VuexySearchBarBuilder::class)->getSearchData());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,15 +36,26 @@ class VuexyNavbarController extends Controller
|
|||||||
{
|
{
|
||||||
abort_if(!request()->expectsJson(), 403, __('errors.ajax_only'));
|
abort_if(!request()->expectsJson(), 403, __('errors.ajax_only'));
|
||||||
|
|
||||||
|
/** @var string Settings Context */
|
||||||
|
$group = 'website-admin';
|
||||||
|
$section = 'layout';
|
||||||
|
$sub_group = 'navbar';
|
||||||
|
|
||||||
|
/** @var string Cache keyName */
|
||||||
|
$key_name = 'quicklinks';
|
||||||
|
|
||||||
|
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
'action' => 'required|in:update,remove',
|
'action' => 'required|in:update,remove',
|
||||||
'route' => 'required|string',
|
'route' => 'required|string',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$key = 'vuexy-quicklinks.user';
|
//$userId = Auth::user()->id;
|
||||||
$userId = Auth::user()->id;
|
|
||||||
|
|
||||||
$quickLinks = settings()->setContext('core', 'navbar')->get($key, $userId)?? [];
|
$quickLinks = settings(CoreModule::COMPONENT)
|
||||||
|
->context($group, $section, $sub_group)
|
||||||
|
->setScope($request->user())
|
||||||
|
->get($key_name)?? [];
|
||||||
|
|
||||||
if ($validated['action'] === 'update') {
|
if ($validated['action'] === 'update') {
|
||||||
if (!in_array($validated['route'], $quickLinks)) {
|
if (!in_array($validated['route'], $quickLinks)) {
|
||||||
@ -58,8 +69,11 @@ class VuexyNavbarController extends Controller
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
settings()->setContext('core', 'navbar')->set($key, json_encode($quickLinks), $userId);
|
settings(CoreModule::COMPONENT)
|
||||||
|
->context($group, $section, $sub_group)
|
||||||
|
->setScope($request->user())
|
||||||
|
->set($key_name, json_encode($quickLinks));
|
||||||
|
|
||||||
VuexyQuicklinksBuilderService::forgetCacheForUser();
|
//VuexyQuicklinksBuilder::forgetCacheForUser();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,146 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Http\Controllers;
|
|
||||||
|
|
||||||
use Illuminate\Routing\Controller;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Laravel\Fortify\Features;
|
|
||||||
|
|
||||||
class UsersAuthController extends Controller
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
public function loginView()
|
|
||||||
{
|
|
||||||
dd($viewMode);
|
|
||||||
$viewMode = config('vuexy.custom.authViewMode');
|
|
||||||
$pageConfigs = ['myLayout' => 'blank'];
|
|
||||||
|
|
||||||
return view("vuexy-admin::auth.login-{$viewMode}", ['pageConfigs' => $pageConfigs]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function registerView()
|
|
||||||
{
|
|
||||||
if (!Features::enabled(Features::registration()))
|
|
||||||
abort(403, 'El registro está deshabilitado.');
|
|
||||||
|
|
||||||
$viewMode = config('vuexy.custom.authViewMode');
|
|
||||||
$pageConfigs = ['myLayout' => 'blank'];
|
|
||||||
|
|
||||||
return view("vuexy-admin::auth.register-{$viewMode}", ['pageConfigs' => $pageConfigs]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function confirmPasswordView()
|
|
||||||
{
|
|
||||||
if (!Features::enabled(Features::registration()))
|
|
||||||
abort(403, 'El registro está deshabilitado.');
|
|
||||||
|
|
||||||
$viewMode = config('vuexy.custom.authViewMode');
|
|
||||||
$pageConfigs = ['myLayout' => 'blank'];
|
|
||||||
|
|
||||||
return view("vuexy-admin::auth.confirm-password-{$viewMode}", ['pageConfigs' => $pageConfigs]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function resetPasswordView()
|
|
||||||
{
|
|
||||||
if (!Features::enabled(Features::resetPasswords()))
|
|
||||||
abort(403, 'El registro está deshabilitado.');
|
|
||||||
|
|
||||||
$viewMode = config('vuexy.custom.authViewMode');
|
|
||||||
$pageConfigs = ['myLayout' => 'blank'];
|
|
||||||
|
|
||||||
return view("vuexy-admin::auth.reset-password-{$viewMode}", ['pageConfigs' => $pageConfigs]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function requestPasswordResetLinkView(Request $request)
|
|
||||||
{
|
|
||||||
if (!Features::enabled(Features::resetPasswords()))
|
|
||||||
abort(403, 'El registro está deshabilitado.');
|
|
||||||
|
|
||||||
$viewMode = config('vuexy.custom.authViewMode');
|
|
||||||
$pageConfigs = ['myLayout' => 'blank'];
|
|
||||||
|
|
||||||
return view("vuexy-admin::auth.reset-password-{$viewMode}", ['pageConfigs' => $pageConfigs, 'request' => $request]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function twoFactorChallengeView()
|
|
||||||
{
|
|
||||||
if (!Features::enabled(Features::registration()))
|
|
||||||
abort(403, 'El registro está deshabilitado.');
|
|
||||||
|
|
||||||
$viewMode = config('vuexy.custom.authViewMode');
|
|
||||||
$pageConfigs = ['myLayout' => 'blank'];
|
|
||||||
|
|
||||||
return view("vuexy-admin::auth.two-factor-challenge-{$viewMode}", ['pageConfigs' => $pageConfigs]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function twoFactorRecoveryCodesView()
|
|
||||||
{
|
|
||||||
if (!Features::enabled(Features::registration()))
|
|
||||||
abort(403, 'El registro está deshabilitado.');
|
|
||||||
|
|
||||||
$viewMode = config('vuexy.custom.authViewMode');
|
|
||||||
$pageConfigs = ['myLayout' => 'blank'];
|
|
||||||
|
|
||||||
return view("vuexy-admin::auth.register-{$viewMode}", ['pageConfigs' => $pageConfigs]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function twoFactorAuthenticationView()
|
|
||||||
{
|
|
||||||
if (!Features::enabled(Features::registration()))
|
|
||||||
abort(403, 'El registro está deshabilitado.');
|
|
||||||
|
|
||||||
$viewMode = config('vuexy.custom.authViewMode');
|
|
||||||
$pageConfigs = ['myLayout' => 'blank'];
|
|
||||||
|
|
||||||
return view("vuexy-admin::auth.register-{$viewMode}", ['pageConfigs' => $pageConfigs]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function verifyEmailView()
|
|
||||||
{
|
|
||||||
if (!Features::enabled(Features::registration()))
|
|
||||||
abort(403, 'El registro está deshabilitado.');
|
|
||||||
|
|
||||||
$viewMode = config('vuexy.custom.authViewMode');
|
|
||||||
$pageConfigs = ['myLayout' => 'blank'];
|
|
||||||
|
|
||||||
return view("vuexy-admin::auth.verify-email-{$viewMode}", ['pageConfigs' => $pageConfigs]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function showEmailVerificationForm()
|
|
||||||
{
|
|
||||||
if (!Features::enabled(Features::registration()))
|
|
||||||
abort(403, 'El registro está deshabilitado.');
|
|
||||||
|
|
||||||
$viewMode = config('vuexy.custom.authViewMode');
|
|
||||||
$pageConfigs = ['myLayout' => 'blank'];
|
|
||||||
|
|
||||||
return view("vuexy-admin::auth.register-{$viewMode}", ['pageConfigs' => $pageConfigs]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function userProfileView()
|
|
||||||
{
|
|
||||||
if (!Features::enabled(Features::registration()))
|
|
||||||
abort(403, 'El registro está deshabilitado.');
|
|
||||||
|
|
||||||
$viewMode = config('vuexy.custom.authViewMode');
|
|
||||||
$pageConfigs = ['myLayout' => 'blank'];
|
|
||||||
|
|
||||||
return view("vuexy-admin::auth.register-{$viewMode}", ['pageConfigs' => $pageConfigs]);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
@ -1,190 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Http\Controllers;
|
|
||||||
|
|
||||||
use Illuminate\Routing\Controller;
|
|
||||||
use Illuminate\Http\JsonResponse;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Illuminate\View\View;
|
|
||||||
use Koneko\VuexyAdmin\Application\UX\Navbar\{VuexyQuicklinksBuilderService,VuexySearchBuilderService};
|
|
||||||
use Koneko\VuexyAdmin\Application\UI\Avatar\AvatarInitialsService;
|
|
||||||
use Koneko\VuexyAdmin\Application\UX\ConfigBuilders\System\EnvironmentVarsTableConfigBuilder;
|
|
||||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controlador para la gestión de funcionalidades administrativas de Vuexy
|
|
||||||
*/
|
|
||||||
class VuexyController extends Controller
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private readonly AvatarInitialsService $avatarService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Realiza búsqueda en la barra de navegación
|
|
||||||
*
|
|
||||||
* @return \Illuminate\Http\JsonResponse
|
|
||||||
* @throws \Illuminate\Http\Exceptions\HttpResponseException
|
|
||||||
*/
|
|
||||||
public function searchNavbar(): JsonResponse
|
|
||||||
{
|
|
||||||
abort_if(!request()->expectsJson(), 403, __('errors.ajax_only'));
|
|
||||||
|
|
||||||
return response()->json(app(VuexySearchBuilderService::class)->getForUser());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Actualiza los enlaces rápidos del usuario
|
|
||||||
*
|
|
||||||
* @param Request $request Datos de la solicitud
|
|
||||||
* @return void
|
|
||||||
* @throws \Illuminate\Http\Exceptions\HttpResponseException
|
|
||||||
* @throws \Illuminate\Validation\ValidationException
|
|
||||||
*/
|
|
||||||
public function quickLinksUpdate(Request $request): void
|
|
||||||
{
|
|
||||||
abort_if(!request()->expectsJson(), 403, __('errors.ajax_only'));
|
|
||||||
|
|
||||||
$validated = $request->validate([
|
|
||||||
'action' => 'required|in:update,remove',
|
|
||||||
'route' => 'required|string',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$quickLinks = settings()->get('quicklinks', Auth::user()->id, []);
|
|
||||||
|
|
||||||
if ($validated['action'] === 'update') {
|
|
||||||
if (!in_array($validated['route'], $quickLinks)) {
|
|
||||||
$quickLinks[] = $validated['route'];
|
|
||||||
}
|
|
||||||
} elseif ($validated['action'] === 'remove') {
|
|
||||||
$quickLinks = array_values(array_filter(
|
|
||||||
$quickLinks,
|
|
||||||
fn($route) => $route !== $validated['route']
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
settings()->set('quicklinks', json_encode($quickLinks), Auth::user()->id, 'vuexy-admin');
|
|
||||||
|
|
||||||
app(VuexyQuicklinksBuilderService::class)->clearCache(Auth::user());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Muestra la vista de configuraciones generales
|
|
||||||
*
|
|
||||||
* @return \Illuminate\View\View
|
|
||||||
*/
|
|
||||||
public function generalSettings(): View
|
|
||||||
{
|
|
||||||
return view('vuexy-admin::general-settings.index');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Muestra la vista de configuraciones SMTP
|
|
||||||
*
|
|
||||||
* @return \Illuminate\View\View
|
|
||||||
*/
|
|
||||||
public function smtpSettings(): View
|
|
||||||
{
|
|
||||||
return view('vuexy-admin::smtp-settings.index');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Muestra la vista de configuraciones de interfaz
|
|
||||||
*
|
|
||||||
* @return \Illuminate\View\View
|
|
||||||
*/
|
|
||||||
public function InterfaceSettings(): View
|
|
||||||
{
|
|
||||||
return view('vuexy-admin::interface-settings.index');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Muestra el listado de accesos al sistema (Bootstrap Table AJAX or View).
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @return \Illuminate\Http\JsonResponse|\Illuminate\View\View
|
|
||||||
*/
|
|
||||||
public function userLogs(Request $request): JsonResponse|View
|
|
||||||
{
|
|
||||||
if ($request->ajax()) {
|
|
||||||
$builder = app(UserLoginTableConfigBuilder::class)->getQueryBuilder($request);
|
|
||||||
|
|
||||||
return $builder->getJson();
|
|
||||||
}
|
|
||||||
|
|
||||||
return view('vuexy-admin::user-logs.index');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Muestra el listado de eventos de auditoría (Bootstrap Table AJAX or View).
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @return \Illuminate\Http\JsonResponse|\Illuminate\View\View
|
|
||||||
*/
|
|
||||||
public function securityEvents(Request $request): JsonResponse|View
|
|
||||||
{
|
|
||||||
if ($request->ajax()) {
|
|
||||||
$builder = app(SecurityEventsTableConfigBuilder::class)->getQueryBuilder($request);
|
|
||||||
|
|
||||||
return $builder->getJson();
|
|
||||||
}
|
|
||||||
|
|
||||||
return view('vuexy-admin::security-events.index');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display a listing of the resource (Bootstrap Table AJAX or View).
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @return \Illuminate\Http\JsonResponse
|
|
||||||
* @return \Illuminate\View\View
|
|
||||||
*/
|
|
||||||
public function environmentVars(Request $request): JsonResponse|View
|
|
||||||
{
|
|
||||||
if ($request->ajax()) {
|
|
||||||
$builder = app(EnvironmentVarsTableConfigBuilder::class)->getQueryBuilder($request);
|
|
||||||
|
|
||||||
return $builder->getJson();
|
|
||||||
}
|
|
||||||
|
|
||||||
return view('vuexy-admin::environment-vars.index');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function generateAvatar(Request $request): BinaryFileResponse
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$validated = $request->validate([
|
|
||||||
'name' => 'required|string|max:255',
|
|
||||||
'color' => 'nullable|string|regex:/^[0-9a-fA-F]{6}$/',
|
|
||||||
'background' => 'nullable|string|regex:/^[0-9a-fA-F]{6}$/',
|
|
||||||
'size' => 'nullable|integer|min:20|max:1024',
|
|
||||||
'max_length' => 'nullable|integer|min:1|max:3'
|
|
||||||
]);
|
|
||||||
|
|
||||||
$response = $this->avatarService->getAvatarImage(
|
|
||||||
name: $validated['name'],
|
|
||||||
forcedColor: $validated['color'] ?? null,
|
|
||||||
forcedBackground: $validated['background'] ?? null,
|
|
||||||
size: $validated['size'] ?? null,
|
|
||||||
maxLength: $validated['max_length'] ?? null
|
|
||||||
);
|
|
||||||
|
|
||||||
$response->headers->set('Cache-Control', 'public, max-age=86400');
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return $this->avatarService->getAvatarImage(
|
|
||||||
name: 'E R R',
|
|
||||||
forcedColor: '#FF0000',
|
|
||||||
maxLength: 3
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -6,30 +6,24 @@ namespace Koneko\VuexyAdmin\Application\Http\Middleware;
|
|||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Support\Facades\View;
|
use Illuminate\Support\Facades\View;
|
||||||
use Koneko\VuexyAdmin\Application\Cache\VuexyVarsBuilderService;
|
use Koneko\VuexyAdmin\Application\Cache\Builders\KonekoAdminVarsBuilder;
|
||||||
use Koneko\VuexyAdmin\Application\UX\Content\VuexyBreadcrumbsBuilderService;
|
use Koneko\VuexyAdmin\Application\UX\Breadcrumbs\VuexyBreadcrumbsBuilder;
|
||||||
use Koneko\VuexyAdmin\Application\UX\Menu\VuexyMenuFormatter;
|
use Koneko\VuexyAdmin\Application\UX\Menu\VuexyMenuFormatter;
|
||||||
use Koneko\VuexyAdmin\Application\UX\Notifications\VuexyNotificationsBuilderService;
|
use Koneko\VuexyAdmin\Application\UX\Notifications\Builder\VuexyNotificationsBuilder;
|
||||||
use Koneko\VuexyAdmin\Application\UX\Template\{VuexyConfigSynchronizer};
|
|
||||||
|
|
||||||
class AdminTemplateMiddleware
|
class AdminTemplateMiddleware
|
||||||
{
|
{
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle($request, Closure $next)
|
public function handle($request, Closure $next)
|
||||||
{
|
{
|
||||||
// Aplicar configuración de layout antes de que la vista se cargue
|
// Aplicar configuración de layout antes de que la vista se cargue
|
||||||
if (str_contains($request->header('Accept'), 'text/html')) {
|
if (str_contains($request->header('Accept'), 'text/html')) {
|
||||||
app(VuexyConfigSynchronizer::class)->sync();
|
config_m()->syncFromRegistry('koneko.core.layout.vuexy');
|
||||||
|
|
||||||
View::share([
|
View::share([
|
||||||
'_admin' => app(VuexyVarsBuilderService::class)->getAdminVars(),
|
'_admin' => app(KonekoAdminVarsBuilder::class)->get(),
|
||||||
'vuexyMenu' => app(VuexyMenuFormatter::class)->getMenu(),
|
'vuexyMenu' => app(VuexyMenuFormatter::class)->getMenu(),
|
||||||
'vuexyNotifications' => app(VuexyNotificationsBuilderService::class)->getForUser(),
|
'vuexyNotifications' => app(VuexyNotificationsBuilder::class)->getNotifications(),
|
||||||
'vuexyBreadcrumbs' => app(VuexyBreadcrumbsBuilderService::class)->getBreadcrumbs(),
|
'vuexyBreadcrumbs' => app(VuexyBreadcrumbsBuilder::class)->getBreadcrumbs(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace Koneko\VuexyAdmin\Application\Listeners\Authentication;
|
namespace Koneko\VuexyAdmin\Application\Listeners\Authentication;
|
||||||
|
|
||||||
use Illuminate\Auth\Events\Failed;
|
use Illuminate\Auth\Events\Failed;
|
||||||
use Koneko\VuexyAdmin\Application\Logger\KonekoSecurityAuditLogger;
|
use Koneko\VuexyAdmin\Application\Loggers\KonekoSecurityAuditLogger;
|
||||||
|
|
||||||
class HandleFailedLogin
|
class HandleFailedLogin
|
||||||
{
|
{
|
||||||
|
@ -5,9 +5,10 @@ declare(strict_types=1);
|
|||||||
namespace Koneko\VuexyAdmin\Application\Listeners\Authentication;
|
namespace Koneko\VuexyAdmin\Application\Listeners\Authentication;
|
||||||
|
|
||||||
use Illuminate\Auth\Events\Logout;
|
use Illuminate\Auth\Events\Logout;
|
||||||
use Koneko\VuexyAdmin\Application\UX\Navbar\{VuexySearchBarBuilderService,VuexyQuicklinksBuilderService};
|
use Koneko\VuexyAdmin\Application\UX\Navbar\VuexySearchBarBuilder;
|
||||||
|
use Koneko\VuexyAdmin\Application\UX\Navbar\VuexyQuicklinksBuilder;
|
||||||
use Koneko\VuexyAdmin\Application\UX\Menu\VuexyMenuFormatter;
|
use Koneko\VuexyAdmin\Application\UX\Menu\VuexyMenuFormatter;
|
||||||
use Koneko\VuexyAdmin\Application\UX\Notifications\VuexyNotificationsBuilderService;
|
use Koneko\VuexyAdmin\Application\UX\Notifications\VuexyNotificationsBuilder;
|
||||||
use Koneko\VuexyAdmin\Models\UserLogin;
|
use Koneko\VuexyAdmin\Models\UserLogin;
|
||||||
|
|
||||||
class HandleUserLogout
|
class HandleUserLogout
|
||||||
@ -28,8 +29,8 @@ class HandleUserLogout
|
|||||||
private function clearUserCaches(int $userId): void
|
private function clearUserCaches(int $userId): void
|
||||||
{
|
{
|
||||||
VuexyMenuFormatter::forgetCacheForUser($userId);
|
VuexyMenuFormatter::forgetCacheForUser($userId);
|
||||||
VuexySearchBarBuilderService::forgetCacheForUser($userId);
|
VuexySearchBarBuilder::forgetCacheForUser($userId);
|
||||||
VuexyQuicklinksBuilderService::clearCacheForUser($userId);
|
VuexyQuicklinksBuilder::clearCacheForUser($userId);
|
||||||
VuexyNotificationsBuilderService::clearCacheForUser($userId);
|
VuexyNotificationsBuilder::clearCacheForUser($userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Listeners\Settings;
|
|
||||||
|
|
||||||
use Koneko\VuexyAdmin\Application\Events\Settings\VuexyCustomizerSettingsUpdated;
|
|
||||||
use Koneko\VuexyAdmin\Application\Cache\VuexyVarsBuilderService;
|
|
||||||
|
|
||||||
class ApplyVuexyCustomizerSettings
|
|
||||||
{
|
|
||||||
public function handle(VuexyCustomizerSettingsUpdated $event): void
|
|
||||||
{
|
|
||||||
foreach ($event->settings as $key => $value) {
|
|
||||||
settings()->self('vuexy')->set($key, $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
VuexyVarsBuilderService::clearCache();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Listeners\Settings;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
use Koneko\VuexyAdmin\Application\Events\Settings\SettingChanged;
|
|
||||||
|
|
||||||
class SettingCacheListener
|
|
||||||
{
|
|
||||||
public function handle(SettingChanged $event): void
|
|
||||||
{
|
|
||||||
$keyHash = md5($event->key);
|
|
||||||
/*
|
|
||||||
$groupHash = md5($event->namespace);
|
|
||||||
$suffix = $event->userId !== null ? "u:{$event->userId}" : 'global';
|
|
||||||
|
|
||||||
Cache::forget("koneko.admin.settings.setting:{$keyHash}:{$suffix}");
|
|
||||||
Cache::forget("koneko.admin.settings.group:{$groupHash}:{$suffix}");
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,82 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Services;
|
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Koneko\VuexyAdmin\Models\SystemLog;
|
|
||||||
use Koneko\VuexyAdmin\Application\Enums\{LogLevel, LogTriggerType};
|
|
||||||
|
|
||||||
class ____SystemLoggerService
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Registra un log en la tabla system_logs.
|
|
||||||
*/
|
|
||||||
public function log(
|
|
||||||
string|LogLevel $level,
|
|
||||||
string $module,
|
|
||||||
string $message,
|
|
||||||
array $context = [],
|
|
||||||
LogTriggerType $triggerType = LogTriggerType::System,
|
|
||||||
?int $triggerId = null,
|
|
||||||
?Model $relatedModel = null
|
|
||||||
): SystemLog {
|
|
||||||
return SystemLog::create([
|
|
||||||
'module' => $module,
|
|
||||||
'level' => $level instanceof LogLevel ? $level : LogLevel::from($level),
|
|
||||||
'message' => $message,
|
|
||||||
'context' => $context,
|
|
||||||
'trigger_type' => $triggerType,
|
|
||||||
'trigger_id' => $triggerId,
|
|
||||||
'user_id' => Auth::id(),
|
|
||||||
'related_model_type' => $relatedModel?->getMorphClass(),
|
|
||||||
'related_model_id' => $relatedModel?->getKey(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function info(
|
|
||||||
string $module,
|
|
||||||
string $message,
|
|
||||||
array $context = [],
|
|
||||||
LogTriggerType $triggerType = LogTriggerType::System,
|
|
||||||
?int $triggerId = null,
|
|
||||||
?Model $relatedModel = null
|
|
||||||
): SystemLog {
|
|
||||||
return $this->log(LogLevel::Info, $module, $message, $context, $triggerType, $triggerId, $relatedModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function warning(
|
|
||||||
string $module,
|
|
||||||
string $message,
|
|
||||||
array $context = [],
|
|
||||||
LogTriggerType $triggerType = LogTriggerType::System,
|
|
||||||
?int $triggerId = null,
|
|
||||||
?Model $relatedModel = null
|
|
||||||
): SystemLog {
|
|
||||||
return $this->log(LogLevel::Warning, $module, $message, $context, $triggerType, $triggerId, $relatedModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function error(
|
|
||||||
string $module,
|
|
||||||
string $message,
|
|
||||||
array $context = [],
|
|
||||||
LogTriggerType $triggerType = LogTriggerType::System,
|
|
||||||
?int $triggerId = null,
|
|
||||||
?Model $relatedModel = null
|
|
||||||
): SystemLog {
|
|
||||||
return $this->log(LogLevel::Error, $module, $message, $context, $triggerType, $triggerId, $relatedModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debug(
|
|
||||||
string $module,
|
|
||||||
string $message,
|
|
||||||
array $context = [],
|
|
||||||
LogTriggerType $triggerType = LogTriggerType::System,
|
|
||||||
?int $triggerId = null,
|
|
||||||
?Model $relatedModel = null
|
|
||||||
): SystemLog {
|
|
||||||
return $this->log(LogLevel::Debug, $module, $message, $context, $triggerType, $triggerId, $relatedModel);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Logger;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Illuminate\Support\Facades\Request;
|
|
||||||
use Koneko\VuexyAdmin\Models\UserInteraction;
|
|
||||||
use Koneko\VuexyAdmin\Application\Enums\UserInteractions\InteractionSecurityLevel;
|
|
||||||
use Koneko\VuexyAdmin\Application\Bootstrap\KonekoModuleRegistry;
|
|
||||||
|
|
||||||
class ____UserInteractionLoggerService
|
|
||||||
{
|
|
||||||
public function record(
|
|
||||||
string $action,
|
|
||||||
array $context = [],
|
|
||||||
InteractionSecurityLevel|string $security = InteractionSecurityLevel::Normal,
|
|
||||||
?string $livewireComponent = null
|
|
||||||
): ?UserInteraction {
|
|
||||||
$user = Auth::user();
|
|
||||||
|
|
||||||
if (!$user) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$component = $this->resolveCurrentComponent();
|
|
||||||
|
|
||||||
return UserInteraction::create([
|
|
||||||
'user_id' => $user->id,
|
|
||||||
'actor_type' => $user->actor_type ?? 'unknown',
|
|
||||||
'component' => $component,
|
|
||||||
'livewire_component' => $livewireComponent,
|
|
||||||
'action' => $action,
|
|
||||||
'security_level' => is_string($security) ? $security : $security->value,
|
|
||||||
'ip_address' => Request::ip(),
|
|
||||||
'user_agent' => Request::userAgent(),
|
|
||||||
'context' => $context,
|
|
||||||
'user_flags' => $user->flags ?? [],
|
|
||||||
'user_roles' => $user->roles ?? [],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Infiera el componente actual (el módulo) para la auditoría.
|
|
||||||
*/
|
|
||||||
private function resolveCurrentComponent(): string
|
|
||||||
{
|
|
||||||
// Opcional: Puedes mejorarlo si quieres tracking de "módulo activo"
|
|
||||||
$modules = KonekoModuleRegistry::enabled();
|
|
||||||
|
|
||||||
if (empty($modules)) {
|
|
||||||
return 'unknown-module';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Si hay muchos módulos activos, podrías basarlo en la ruta actual
|
|
||||||
$currentRoute = request()->route()?->getName();
|
|
||||||
|
|
||||||
foreach ($modules as $module) {
|
|
||||||
if (str_contains($currentRoute, $module->slug)) {
|
|
||||||
return $module->getId(); // Este es slugificado
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: solo devuelve el primero activo
|
|
||||||
return reset($modules)?->getId() ?? 'unknown-module';
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Logger;
|
namespace Koneko\VuexyAdmin\Application\Loggers;
|
||||||
|
|
||||||
use GeoIp2\Database\Reader;
|
use GeoIp2\Database\Reader;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
@ -1,13 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Support\Logger;
|
namespace Koneko\VuexyAdmin\Application\Loggers;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Jenssegers\Agent\Agent;
|
use Jenssegers\Agent\Agent;
|
||||||
use Koneko\VuexyAdmin\Application\Enums\SecurityEvents\SecurityEventStatus;
|
use Koneko\VuexyAdmin\Support\Enums\SecurityEvents\SecurityEventStatus;
|
||||||
use Koneko\VuexyAdmin\Application\Enums\SecurityEvents\SecurityEventType;
|
use Koneko\VuexyAdmin\Support\Enums\SecurityEvents\SecurityEventType;
|
||||||
use Koneko\VuexyAdmin\Application\Bootstrap\KonekoComponentContextRegistrar;
|
use Koneko\VuexyAdmin\Application\Bootstrap\Registry\KonekoModuleRegistry;
|
||||||
use Koneko\VuexyAdmin\Models\SecurityEvent;
|
use Koneko\VuexyAdmin\Models\SecurityEvent;
|
||||||
use Koneko\VuexyAdmin\Support\Geo\GeoLocationResolver;
|
use Koneko\VuexyAdmin\Support\Geo\GeoLocationResolver;
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ class KonekoSecurityLogger
|
|||||||
$geo = GeoLocationResolver::resolve($request->ip());
|
$geo = GeoLocationResolver::resolve($request->ip());
|
||||||
|
|
||||||
return SecurityEvent::create([
|
return SecurityEvent::create([
|
||||||
'module' => KonekoComponentContextRegistrar::currentComponent() ?? 'unknown',
|
'module' => KonekoModuleRegistry::current()->componentNamespace ?? 'unknown',
|
||||||
'user_id' => $userId ?? Auth::id(),
|
'user_id' => $userId ?? Auth::id(),
|
||||||
'event_type' => (string) $type,
|
'event_type' => (string) $type,
|
||||||
'status' => SecurityEventStatus::NEW,
|
'status' => SecurityEventStatus::NEW,
|
@ -1,14 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Alication\Logger;
|
namespace Koneko\VuexyAdmin\Application\Loggers;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Illuminate\Support\Facades\App;
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Http\Request;
|
use Koneko\VuexyAdmin\Application\Bootstrap\Registry\KonekoModuleRegistry;
|
||||||
use Koneko\VuexyAdmin\Application\Enums\SystemLog\{LogTriggerType, LogLevel};
|
|
||||||
use Koneko\VuexyAdmin\Application\Bootstrap\KonekoComponentContextRegistrar;
|
|
||||||
use Koneko\VuexyAdmin\Models\SystemLog;
|
use Koneko\VuexyAdmin\Models\SystemLog;
|
||||||
|
use Koneko\VuexyAdmin\Support\Enums\SystemLog\LogLevel;
|
||||||
|
use Koneko\VuexyAdmin\Support\Enums\SystemLog\LogTriggerType;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ✨ Logger de sistema contextual para el ecosistema Koneko
|
* ✨ Logger de sistema contextual para el ecosistema Koneko
|
||||||
@ -19,7 +18,7 @@ class KonekoSystemLogger
|
|||||||
|
|
||||||
public function __construct(?string $component = null)
|
public function __construct(?string $component = null)
|
||||||
{
|
{
|
||||||
$this->component = $component ?? KonekoComponentContextRegistrar::currentComponent() ?? 'core';
|
$this->component = $component ?? KonekoModuleRegistry::current()->componentNamespace ?? 'core';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function log(
|
public function log(
|
||||||
@ -32,7 +31,7 @@ class KonekoSystemLogger
|
|||||||
): SystemLog {
|
): SystemLog {
|
||||||
return SystemLog::create([
|
return SystemLog::create([
|
||||||
'module' => $this->component,
|
'module' => $this->component,
|
||||||
'user_id' => auth()->id(),
|
'user_id' => Auth::id(),
|
||||||
'level' => $level instanceof LogLevel ? $level->value : $level,
|
'level' => $level instanceof LogLevel ? $level->value : $level,
|
||||||
'message' => $message,
|
'message' => $message,
|
||||||
'context' => $context,
|
'context' => $context,
|
@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Logger;
|
namespace Koneko\VuexyAdmin\Application\Loggers;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Request;
|
use Illuminate\Support\Facades\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Koneko\VuexyAdmin\Application\Enums\UserInteractions\InteractionSecurityLevel;
|
use Koneko\VuexyAdmin\Application\Bootstrap\Registry\KonekoModuleRegistry;
|
||||||
use Koneko\VuexyAdmin\Application\Bootstrap\KonekoComponentContextRegistrar;
|
|
||||||
use Koneko\VuexyAdmin\Models\UserInteraction;
|
use Koneko\VuexyAdmin\Models\UserInteraction;
|
||||||
use Koneko\VuexyAdmin\Models\User;
|
use Koneko\VuexyAdmin\Models\User;
|
||||||
|
use Koneko\VuexyAdmin\Support\Enums\UserInteractions\InteractionSecurityLevel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 📋 Logger de interacciones de usuario con nivel de seguridad.
|
* 📋 Logger de interacciones de usuario con nivel de seguridad.
|
||||||
@ -32,7 +32,7 @@ class KonekoUserInteractionLogger
|
|||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
|
|
||||||
return UserInteraction::create([
|
return UserInteraction::create([
|
||||||
'module' => KonekoComponentContextRegistrar::currentComponent(),
|
'module' => KonekoModuleRegistry::current()->componentNamespace ?? 'core',
|
||||||
'user_id' => $userId,
|
'user_id' => $userId,
|
||||||
'action' => $action,
|
'action' => $action,
|
||||||
'livewire_component'=> $livewireComponent,
|
'livewire_component'=> $livewireComponent,
|
@ -1,29 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Logger;
|
|
||||||
|
|
||||||
use Illuminate\Support\ServiceProvider;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Koneko\VuexyAdmin\Application\Services\SystemLoggerService;
|
|
||||||
|
|
||||||
class LogMacrosServiceProvider extends ServiceProvider
|
|
||||||
{
|
|
||||||
public function boot(): void
|
|
||||||
{
|
|
||||||
// Macro principal: log central
|
|
||||||
Log::macro('system', function (): SystemLoggerService {
|
|
||||||
return app(SystemLoggerService::class);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 🔧 Ejemplo de uso inmediato (puedes remover o modificar):
|
|
||||||
/*
|
|
||||||
Log::system()->info(
|
|
||||||
module: 'vuexy-admin',
|
|
||||||
message: 'Sistema de macros de log inicializado correctamente',
|
|
||||||
context: ['environment' => app()->environment()]
|
|
||||||
);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Macros;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\App;
|
|
||||||
|
|
||||||
App::macro('settings', function () {
|
|
||||||
$settingsService = app(\Koneko\VuexyAdmin\Application\System\SettingsService::class);
|
|
||||||
|
|
||||||
return new class($settingsService) {
|
|
||||||
public function __construct(private $settingsService) {}
|
|
||||||
|
|
||||||
public function get(string $key, ...$args): mixed
|
|
||||||
{
|
|
||||||
return $this->settingsService->get($key, ...$args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function set(string $key, mixed $value, ...$args): mixed
|
|
||||||
{
|
|
||||||
return $this->settingsService->set($key, $value, ...$args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function admin(): object
|
|
||||||
{
|
|
||||||
return new class($this->settingsService) {
|
|
||||||
public function __construct(private $settingsService) {}
|
|
||||||
|
|
||||||
public function get(string $key, ...$args): mixed
|
|
||||||
{
|
|
||||||
return $this->settingsService->get('koneko.admin.' . $key, ...$args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function set(string $key, mixed $value, ...$args): mixed
|
|
||||||
{
|
|
||||||
return $this->settingsService->set('koneko.admin.' . $key, $value, ...$args);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
@ -1,27 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Macros;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
Log::macro('vuexyAdminLogger', function () {
|
|
||||||
return new class {
|
|
||||||
public function info(string $message, array $context = []) {
|
|
||||||
return Log::system()->info('vuexy-admin', $message, $context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function warning(string $message, array $context = []) {
|
|
||||||
return Log::system()->warning('vuexy-admin', $message, $context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function error(string $message, array $context = []) {
|
|
||||||
return Log::system()->error('vuexy-admin', $message, $context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debug(string $message, array $context = []) {
|
|
||||||
return Log::system()->debug('vuexy-admin', $message, $context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
@ -1,167 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Macros;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\App;
|
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Koneko\VuexyAdmin\Application\Contracts\Settings\SettingsRepositoryInterface;
|
|
||||||
|
|
||||||
App::macro('vuexySettings', function () {
|
|
||||||
$settingsService = app(SettingsRepositoryInterface::class);
|
|
||||||
|
|
||||||
return new class($settingsService) {
|
|
||||||
/**
|
|
||||||
* @var string Default namespace assigned during module boot
|
|
||||||
*/
|
|
||||||
private string $defaultNamespace = '';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string|null Current namespace used for operations
|
|
||||||
*/
|
|
||||||
private ?string $currentNamespace = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* @param SettingsRepositoryInterface $settingsService
|
|
||||||
*/
|
|
||||||
public function __construct(private SettingsRepositoryInterface $settingsService) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the default namespace (typically from the module at boot time).
|
|
||||||
*
|
|
||||||
* @param string $namespace
|
|
||||||
* @return self
|
|
||||||
*/
|
|
||||||
public function setDefaultNamespace(string $namespace): self
|
|
||||||
{
|
|
||||||
$this->defaultNamespace = rtrim($namespace, '.') . '.';
|
|
||||||
$this->currentNamespace = $this->defaultNamespace;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the namespace to the module's own namespace.
|
|
||||||
*
|
|
||||||
* @return self
|
|
||||||
*/
|
|
||||||
public function self(): self
|
|
||||||
{
|
|
||||||
$this->currentNamespace = $this->defaultNamespace;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Switches the namespace to a custom one.
|
|
||||||
*
|
|
||||||
* @param string $namespace
|
|
||||||
* @return self
|
|
||||||
*/
|
|
||||||
public function in(string $namespace): self
|
|
||||||
{
|
|
||||||
$this->currentNamespace = rtrim($namespace, '.') . '.';
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a setting key with the current namespace applied.
|
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
* @param mixed ...$args
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function get(string $key, ...$args): mixed
|
|
||||||
{
|
|
||||||
return $this->settingsService->get($this->qualifyKey($key), ...$args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a setting key with the current namespace applied.
|
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
* @param mixed $value
|
|
||||||
* @param mixed ...$args
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function set(string $key, mixed $value, ...$args): mixed
|
|
||||||
{
|
|
||||||
return $this->settingsService->set($this->qualifyKey($key), $value, ...$args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lists all groups (namespaces) available.
|
|
||||||
*
|
|
||||||
* @return SettingsGroupCollection
|
|
||||||
*/
|
|
||||||
public function listGroups(): SettingsGroupCollection
|
|
||||||
{
|
|
||||||
// Extraemos todos los keys agrupados
|
|
||||||
$allSettings = $this->settingsService->getGroup('');
|
|
||||||
|
|
||||||
// Mapeamos
|
|
||||||
$groups = collect($allSettings)
|
|
||||||
->keys()
|
|
||||||
->map(function ($key) {
|
|
||||||
$parts = explode('.', $key);
|
|
||||||
return $parts[0] ?? 'unknown';
|
|
||||||
})
|
|
||||||
->unique()
|
|
||||||
->values();
|
|
||||||
|
|
||||||
// Aplicamos filtro si es namespace específico
|
|
||||||
if ($this->currentNamespace !== null) {
|
|
||||||
$namespaceRoot = rtrim($this->currentNamespace, '.');
|
|
||||||
$groups = $groups->filter(fn($group) => $group === $namespaceRoot);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SettingsGroupCollection($groups);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current namespace.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function currentNamespace(): string
|
|
||||||
{
|
|
||||||
return $this->currentNamespace ?? $this->defaultNamespace;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal method to prepend namespace to key.
|
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function qualifyKey(string $key): string
|
|
||||||
{
|
|
||||||
return $this->currentNamespace . $key;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Small helper class for group collection.
|
|
||||||
*/
|
|
||||||
class SettingsGroupCollection extends Collection
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Adds details (number of keys per group, etc.) to each group.
|
|
||||||
*
|
|
||||||
* @return Collection
|
|
||||||
*/
|
|
||||||
public function details(): Collection
|
|
||||||
{
|
|
||||||
return $this->map(function ($group) {
|
|
||||||
// Aquí puedes agregar más metadata en el futuro
|
|
||||||
return [
|
|
||||||
'name' => $group,
|
|
||||||
'key_prefix' => $group . '.',
|
|
||||||
'total_keys' => settings()->in($group)->countKeys(),
|
|
||||||
];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,11 +2,12 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\RBAC;
|
namespace Koneko\VuexyAdmin\Application\RBAC\Sync;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\File;
|
use Illuminate\Support\Facades\File;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Koneko\VuexyAdmin\Application\Bootstrap\{KonekoModuleBootManager, KonekoModuleRegistry};
|
use Koneko\VuexyAdmin\Application\Bootstrap\Manager\KonekoModuleBootManager;
|
||||||
|
use Koneko\VuexyAdmin\Application\Bootstrap\Registry\KonekoModuleRegistry;
|
||||||
use Koneko\VuexyAdmin\Models\{PermissionMeta, RoleMeta, PermissionGroup};
|
use Koneko\VuexyAdmin\Models\{PermissionMeta, RoleMeta, PermissionGroup};
|
||||||
use Spatie\Permission\Exceptions\PermissionDoesNotExist;
|
use Spatie\Permission\Exceptions\PermissionDoesNotExist;
|
||||||
|
|
@ -1,64 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Security;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Crypt;
|
|
||||||
use Koneko\VuexyAdmin\Models\VaultKey;
|
|
||||||
|
|
||||||
class VaultKeyService
|
|
||||||
{
|
|
||||||
public function generateKey(
|
|
||||||
string $alias,
|
|
||||||
string $ownerProject = 'default_project',
|
|
||||||
string $algorithm = 'AES-256-CBC',
|
|
||||||
bool $isSensitive = true
|
|
||||||
): VaultKey {
|
|
||||||
if (!in_array($algorithm, openssl_get_cipher_methods())) {
|
|
||||||
throw new \InvalidArgumentException("Algoritmo no soportado: $algorithm");
|
|
||||||
}
|
|
||||||
|
|
||||||
$keyMaterial = random_bytes(openssl_cipher_iv_length($algorithm) * 2);
|
|
||||||
$encryptedKey = Crypt::encrypt($keyMaterial);
|
|
||||||
|
|
||||||
return VaultKey::create([
|
|
||||||
'environment' => app()->environment(),
|
|
||||||
'namespace' => config('app.name'),
|
|
||||||
'scope' => 'global',
|
|
||||||
'alias' => $alias,
|
|
||||||
'owner_project' => $ownerProject,
|
|
||||||
'algorithm' => $algorithm,
|
|
||||||
'key_material' => $encryptedKey,
|
|
||||||
'is_active' => true,
|
|
||||||
'is_sensitive' => $isSensitive,
|
|
||||||
'rotated_at' => now(),
|
|
||||||
'rotation_count' => 0,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function retrieveKey(string $alias): string
|
|
||||||
{
|
|
||||||
$keyEntry = VaultKey::where('alias', $alias)->where('is_active', true)->firstOrFail();
|
|
||||||
|
|
||||||
return Crypt::decrypt($keyEntry->key_material);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function rotateKey(string $alias): VaultKey
|
|
||||||
{
|
|
||||||
$keyEntry = VaultKey::where('alias', $alias)->firstOrFail();
|
|
||||||
|
|
||||||
$newKeyMaterial = random_bytes(openssl_cipher_iv_length($keyEntry->algorithm) * 2);
|
|
||||||
|
|
||||||
$keyEntry->update([
|
|
||||||
'key_material' => Crypt::encrypt($newKeyMaterial),
|
|
||||||
'rotated_at' => now(),
|
|
||||||
'rotation_count' => $keyEntry->rotation_count + 1,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return $keyEntry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function deactivateKey(string $alias): bool
|
|
||||||
{
|
|
||||||
return VaultKey::where('alias', $alias)->update(['is_active' => false]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Traits\Seeders\Main;
|
namespace Koneko\VuexyAdmin\Application\Seeding\Concerns\Main;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\File;
|
use Illuminate\Support\Facades\File;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Traits\Seeders\Main;
|
namespace Koneko\VuexyAdmin\Application\Seeding\Concerns\Main;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ⚡ Trait para soporte de procesamiento por lotes (chunks) en seeders.
|
* ⚡ Trait para soporte de procesamiento por lotes (chunks) en seeders.
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Application\Traits\Seeders\Main;
|
namespace Koneko\VuexyAdmin\Application\Seeding\Concerns\Main;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trait para generación de registros faker vía Factory con fallback.
|
* Trait para generación de registros faker vía Factory con fallback.
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user