jerkjs 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/LICENSE +200 -0
  2. package/README.md +171 -0
  3. package/doc/EXTENSION_MANUAL.md +958 -0
  4. package/doc/FIREWALL_MANUAL.md +419 -0
  5. package/doc/HOOKS_REFERENCE_IMPROVED.md +599 -0
  6. package/doc/MANUAL_API_SDK.md +539 -0
  7. package/doc/MANUAL_MVC.md +397 -0
  8. package/doc/MARIADB_TOKENS_IMPLEMENTATION.md +113 -0
  9. package/doc/MIDDLEWARE_MANUAL.md +521 -0
  10. package/doc/OAUTH2_GOOGLE_MANUAL.md +408 -0
  11. package/doc/frontend-and-sessions.md +356 -0
  12. package/examples/advanced/controllers/productController.js +64 -0
  13. package/examples/advanced/controllers/userController.js +85 -0
  14. package/examples/advanced/routes.json +51 -0
  15. package/examples/advanced_example.js +93 -0
  16. package/examples/basic/controllers/userController.js +85 -0
  17. package/examples/basic_example.js +72 -0
  18. package/examples/frontend/README.md +71 -0
  19. package/examples/frontend/app.js +71 -0
  20. package/examples/frontend/controllers/apiController.js +39 -0
  21. package/examples/frontend/controllers/authController.js +220 -0
  22. package/examples/frontend/controllers/formController.js +47 -0
  23. package/examples/frontend/controllers/messageController.js +96 -0
  24. package/examples/frontend/controllers/pageController.js +178 -0
  25. package/examples/frontend/controllers/staticController.js +167 -0
  26. package/examples/frontend/routes.json +90 -0
  27. package/examples/mvc_example/app.js +138 -0
  28. package/examples/mvc_example/views/home/index.html +26 -0
  29. package/examples/mvc_example/views/home/simple.html +3 -0
  30. package/examples/mvc_example/views/layout.html +23 -0
  31. package/examples/mvc_example/views/test.html +3 -0
  32. package/examples/mvc_example/views/user/invalid.html +6 -0
  33. package/examples/mvc_example/views/user/list.html +36 -0
  34. package/examples/mvc_example/views/user/notfound.html +6 -0
  35. package/examples/mvc_example/views/user/profile.html +11 -0
  36. package/examples/mvc_routes_example/app.js +34 -0
  37. package/examples/mvc_routes_example/controllers/mainController.js +27 -0
  38. package/examples/mvc_routes_example/controllers/productController.js +47 -0
  39. package/examples/mvc_routes_example/controllers/userController.js +76 -0
  40. package/examples/mvc_routes_example/routes.json +30 -0
  41. package/examples/mvc_routes_example/views/layout.html +31 -0
  42. package/examples/mvc_routes_example/views/main/index.html +11 -0
  43. package/examples/mvc_routes_example/views/product/catalog.html +24 -0
  44. package/examples/mvc_routes_example/views/user/invalid.html +6 -0
  45. package/examples/mvc_routes_example/views/user/list.html +40 -0
  46. package/examples/mvc_routes_example/views/user/notfound.html +6 -0
  47. package/examples/mvc_routes_example/views/user/profile.html +18 -0
  48. package/examples/public/README.md +92 -0
  49. package/examples/public/app.js +72 -0
  50. package/examples/public/controllers/healthController.js +20 -0
  51. package/examples/public/controllers/mainController.js +22 -0
  52. package/examples/public/controllers/userController.js +139 -0
  53. package/examples/public/routes.json +51 -0
  54. package/examples/v2/README.md +72 -0
  55. package/examples/v2/app.js +74 -0
  56. package/examples/v2/app_fixed.js +74 -0
  57. package/examples/v2/controllers/authController.js +64 -0
  58. package/examples/v2/controllers/mainController.js +24 -0
  59. package/examples/v2/controllers/protectedController.js +12 -0
  60. package/examples/v2/controllers/userController.js +16 -0
  61. package/examples/v2/package.json +27 -0
  62. package/examples/v2/routes.json +30 -0
  63. package/examples/v2/test_api.sh +47 -0
  64. package/examples/v2/tokens_example.sqlite +0 -0
  65. package/examples/v2.1_firewall_demo/README.md +113 -0
  66. package/examples/v2.1_firewall_demo/app.js +182 -0
  67. package/examples/v2.1_firewall_demo/package.json +27 -0
  68. package/examples/v2.1_hooks_demo/README.md +85 -0
  69. package/examples/v2.1_hooks_demo/app.js +101 -0
  70. package/examples/v2.1_hooks_demo/controllers/hooksController.js +29 -0
  71. package/examples/v2.1_hooks_demo/controllers/mainController.js +18 -0
  72. package/examples/v2.1_hooks_demo/package.json +27 -0
  73. package/examples/v2.1_hooks_demo/routes.json +16 -0
  74. package/examples/v2.1_openapi_demo/README.md +82 -0
  75. package/examples/v2.1_openapi_demo/app.js +296 -0
  76. package/examples/v2.1_openapi_demo/package.json +26 -0
  77. package/examples/v2_cors/README.md +82 -0
  78. package/examples/v2_cors/app.js +108 -0
  79. package/examples/v2_cors/package.json +23 -0
  80. package/examples/v2_json_auth/README.md +83 -0
  81. package/examples/v2_json_auth/app.js +72 -0
  82. package/examples/v2_json_auth/controllers/authController.js +67 -0
  83. package/examples/v2_json_auth/controllers/mainController.js +16 -0
  84. package/examples/v2_json_auth/controllers/protectedController.js +12 -0
  85. package/examples/v2_json_auth/controllers/tokenController.js +28 -0
  86. package/examples/v2_json_auth/controllers/userController.js +15 -0
  87. package/examples/v2_json_auth/package.json +26 -0
  88. package/examples/v2_json_auth/routes.json +37 -0
  89. package/examples/v2_json_auth/tokens.json +20 -0
  90. package/examples/v2_mariadb_auth/README.md +94 -0
  91. package/examples/v2_mariadb_auth/app.js +81 -0
  92. package/examples/v2_mariadb_auth/controllers/authController.js +95 -0
  93. package/examples/v2_mariadb_auth/controllers/mainController.js +31 -0
  94. package/examples/v2_mariadb_auth/controllers/protectedController.js +12 -0
  95. package/examples/v2_mariadb_auth/controllers/userController.js +17 -0
  96. package/examples/v2_mariadb_auth/package.json +27 -0
  97. package/examples/v2_mariadb_auth/routes.json +37 -0
  98. package/examples/v2_no_auth/README.md +75 -0
  99. package/examples/v2_no_auth/app.js +72 -0
  100. package/examples/v2_no_auth/controllers/healthController.js +14 -0
  101. package/examples/v2_no_auth/controllers/mainController.js +19 -0
  102. package/examples/v2_no_auth/controllers/productController.js +31 -0
  103. package/examples/v2_no_auth/controllers/publicController.js +16 -0
  104. package/examples/v2_no_auth/package.json +22 -0
  105. package/examples/v2_no_auth/routes.json +37 -0
  106. package/examples/v2_oauth/README.md +70 -0
  107. package/examples/v2_oauth/app.js +90 -0
  108. package/examples/v2_oauth/controllers/mainController.js +45 -0
  109. package/examples/v2_oauth/controllers/oauthController.js +247 -0
  110. package/examples/v2_oauth/controllers/protectedController.js +13 -0
  111. package/examples/v2_oauth/controllers/userController.js +17 -0
  112. package/examples/v2_oauth/package.json +26 -0
  113. package/examples/v2_oauth/routes.json +44 -0
  114. package/examples/v2_openapi/README.md +77 -0
  115. package/examples/v2_openapi/app.js +222 -0
  116. package/examples/v2_openapi/controllers/authController.js +52 -0
  117. package/examples/v2_openapi/controllers/mainController.js +26 -0
  118. package/examples/v2_openapi/controllers/productController.js +17 -0
  119. package/examples/v2_openapi/controllers/userController.js +27 -0
  120. package/examples/v2_openapi/package.json +26 -0
  121. package/examples/v2_openapi/routes.json +37 -0
  122. package/generate_token.js +10 -0
  123. package/index.js +85 -0
  124. package/jerk.jpg +0 -0
  125. package/lib/core/handler.js +86 -0
  126. package/lib/core/hooks.js +224 -0
  127. package/lib/core/router.js +204 -0
  128. package/lib/core/securityEnhancedServer.js +752 -0
  129. package/lib/core/server.js +369 -0
  130. package/lib/loader/controllerLoader.js +175 -0
  131. package/lib/loader/routeLoader.js +341 -0
  132. package/lib/middleware/auditLogger.js +208 -0
  133. package/lib/middleware/authenticator.js +565 -0
  134. package/lib/middleware/compressor.js +218 -0
  135. package/lib/middleware/cors.js +135 -0
  136. package/lib/middleware/firewall.js +443 -0
  137. package/lib/middleware/rateLimiter.js +210 -0
  138. package/lib/middleware/session.js +301 -0
  139. package/lib/middleware/validator.js +193 -0
  140. package/lib/mvc/controllerBase.js +207 -0
  141. package/lib/mvc/viewEngine.js +752 -0
  142. package/lib/utils/configParser.js +223 -0
  143. package/lib/utils/logger.js +145 -0
  144. package/lib/utils/mariadbTokenAdapter.js +226 -0
  145. package/lib/utils/openapiGenerator.js +140 -0
  146. package/lib/utils/sqliteTokenAdapter.js +224 -0
  147. package/lib/utils/tokenManager.js +254 -0
  148. package/package.json +47 -0
  149. package/v2examplle/v2_json_auth/README.md +83 -0
  150. package/v2examplle/v2_json_auth/app.js +72 -0
  151. package/v2examplle/v2_json_auth/controllers/authController.js +67 -0
  152. package/v2examplle/v2_json_auth/controllers/mainController.js +16 -0
  153. package/v2examplle/v2_json_auth/controllers/protectedController.js +12 -0
  154. package/v2examplle/v2_json_auth/controllers/tokenController.js +28 -0
  155. package/v2examplle/v2_json_auth/controllers/userController.js +15 -0
  156. package/v2examplle/v2_json_auth/package.json +26 -0
  157. package/v2examplle/v2_json_auth/routes.json +37 -0
  158. package/v2examplle/v2_json_auth/tokens.json +20 -0
  159. package/v2examplle/v2_mariadb_auth/README.md +94 -0
  160. package/v2examplle/v2_mariadb_auth/app.js +81 -0
  161. package/v2examplle/v2_mariadb_auth/controllers/authController.js +95 -0
  162. package/v2examplle/v2_mariadb_auth/controllers/mainController.js +31 -0
  163. package/v2examplle/v2_mariadb_auth/controllers/protectedController.js +12 -0
  164. package/v2examplle/v2_mariadb_auth/controllers/userController.js +17 -0
  165. package/v2examplle/v2_mariadb_auth/package.json +27 -0
  166. package/v2examplle/v2_mariadb_auth/routes.json +37 -0
  167. package/v2examplle/v2_sqlite_auth/README.md +72 -0
  168. package/v2examplle/v2_sqlite_auth/app.js +74 -0
  169. package/v2examplle/v2_sqlite_auth/app_fixed.js +74 -0
  170. package/v2examplle/v2_sqlite_auth/controllers/authController.js +64 -0
  171. package/v2examplle/v2_sqlite_auth/controllers/mainController.js +24 -0
  172. package/v2examplle/v2_sqlite_auth/controllers/protectedController.js +12 -0
  173. package/v2examplle/v2_sqlite_auth/controllers/userController.js +16 -0
  174. package/v2examplle/v2_sqlite_auth/package.json +27 -0
  175. package/v2examplle/v2_sqlite_auth/routes.json +30 -0
  176. package/v2examplle/v2_sqlite_auth/test_api.sh +47 -0
  177. package/v2examplle/v2_sqlite_auth/tokens_example.sqlite +0 -0
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Parser de configuración para el framework API SDK
3
+ * Implementación del componente utils/configParser.js
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ class ConfigParser {
10
+ /**
11
+ * Constructor del parser de configuración
12
+ */
13
+ constructor() {
14
+ this.config = {};
15
+ }
16
+
17
+ /**
18
+ * Método para cargar configuración desde un archivo JSON
19
+ * @param {string} filePath - Ruta al archivo de configuración
20
+ * @returns {Object} - Configuración cargada
21
+ */
22
+ loadFromFile(filePath) {
23
+ try {
24
+ if (!fs.existsSync(filePath)) {
25
+ throw new Error(`Archivo de configuración no encontrado: ${filePath}`);
26
+ }
27
+
28
+ const configData = fs.readFileSync(filePath, 'utf8');
29
+ const parsedConfig = JSON.parse(configData);
30
+
31
+ this.config = { ...this.config, ...parsedConfig };
32
+ return this.config;
33
+ } catch (error) {
34
+ throw new Error(`Error cargando configuración desde ${filePath}: ${error.message}`);
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Método para cargar configuración desde variables de entorno
40
+ * @param {Object} env - Objeto con variables de entorno (por defecto process.env)
41
+ * @param {Object} mapping - Objeto que mapea variables de entorno a claves de configuración
42
+ * @returns {Object} - Configuración actualizada
43
+ */
44
+ loadFromEnv(env = process.env, mapping = {}) {
45
+ const envConfig = {};
46
+
47
+ // Si se proporciona un mapeo, usarlo para leer variables de entorno
48
+ if (Object.keys(mapping).length > 0) {
49
+ for (const [configKey, envVar] of Object.entries(mapping)) {
50
+ if (env[envVar] !== undefined) {
51
+ envConfig[configKey] = this.parseEnvValue(env[envVar]);
52
+ }
53
+ }
54
+ } else {
55
+ // Si no hay mapeo, buscar variables con prefijo API_
56
+ for (const [key, value] of Object.entries(env)) {
57
+ if (key.startsWith('API_')) {
58
+ const configKey = this.snakeToCamel(key.substring(4).toLowerCase()); // Remover API_ y convertir
59
+ envConfig[configKey] = this.parseEnvValue(value);
60
+ }
61
+ }
62
+ }
63
+
64
+ this.config = { ...this.config, ...envConfig };
65
+ return this.config;
66
+ }
67
+
68
+ /**
69
+ * Método para parsear valores de variables de entorno
70
+ * @param {string} value - Valor de la variable de entorno
71
+ * @returns {*} - Valor parseado (string, number, boolean, etc.)
72
+ */
73
+ parseEnvValue(value) {
74
+ // Intentar parsear como booleano
75
+ if (value.toLowerCase() === 'true') return true;
76
+ if (value.toLowerCase() === 'false') return false;
77
+
78
+ // Intentar parsear como número
79
+ if (!isNaN(value) && value.trim() !== '') {
80
+ const numValue = Number(value);
81
+ if (!isNaN(numValue)) return numValue;
82
+ }
83
+
84
+ // Si contiene comas, podría ser un array
85
+ if (value.includes(',')) {
86
+ return value.split(',').map(item => this.parseEnvValue(item.trim()));
87
+ }
88
+
89
+ // Por defecto, retornar como string
90
+ return value;
91
+ }
92
+
93
+ /**
94
+ * Método para convertir snake_case a camelCase
95
+ * @param {string} str - Cadena en formato snake_case
96
+ * @returns {string} - Cadena en formato camelCase
97
+ */
98
+ snakeToCamel(str) {
99
+ return str.replace(/_([a-z])/g, (match, letter) => letter.toUpperCase());
100
+ }
101
+
102
+ /**
103
+ * Método para cargar configuración por defecto
104
+ * @param {Object} defaults - Configuración por defecto
105
+ * @returns {Object} - Configuración actualizada
106
+ */
107
+ loadDefaults(defaults) {
108
+ this.config = { ...defaults, ...this.config };
109
+ return this.config;
110
+ }
111
+
112
+ /**
113
+ * Método para obtener un valor de configuración
114
+ * @param {string} key - Clave de la configuración (puede usar notación de punto para objetos anidados)
115
+ * @param {*} defaultValue - Valor por defecto si no se encuentra la clave
116
+ * @returns {*} - Valor de la configuración
117
+ */
118
+ get(key, defaultValue = undefined) {
119
+ // Soportar notación de punto para acceder a propiedades anidadas
120
+ const keys = key.split('.');
121
+ let value = this.config;
122
+
123
+ for (const k of keys) {
124
+ if (value === null || value === undefined || typeof value !== 'object') {
125
+ return defaultValue;
126
+ }
127
+ value = value[k];
128
+ }
129
+
130
+ return value !== undefined ? value : defaultValue;
131
+ }
132
+
133
+ /**
134
+ * Método para establecer un valor de configuración
135
+ * @param {string} key - Clave de la configuración (puede usar notación de punto para objetos anidados)
136
+ * @param {*} value - Valor a establecer
137
+ */
138
+ set(key, value) {
139
+ // Soportar notación de punto para establecer propiedades anidadas
140
+ const keys = key.split('.');
141
+ let current = this.config;
142
+
143
+ for (let i = 0; i < keys.length - 1; i++) {
144
+ const k = keys[i];
145
+ if (current[k] === undefined || current[k] === null) {
146
+ current[k] = {};
147
+ }
148
+ current = current[k];
149
+ }
150
+
151
+ current[keys[keys.length - 1]] = value;
152
+ }
153
+
154
+ /**
155
+ * Método para obtener toda la configuración
156
+ * @returns {Object} - Objeto con toda la configuración
157
+ */
158
+ getAll() {
159
+ return { ...this.config };
160
+ }
161
+
162
+ /**
163
+ * Método para reiniciar la configuración
164
+ */
165
+ reset() {
166
+ this.config = {};
167
+ }
168
+
169
+ /**
170
+ * Método para validar la configuración contra un esquema
171
+ * @param {Object} schema - Esquema de validación
172
+ * @returns {Array} - Array de errores de validación
173
+ */
174
+ validate(schema) {
175
+ const errors = [];
176
+
177
+ for (const [key, rules] of Object.entries(schema)) {
178
+ const value = this.get(key);
179
+
180
+ if (rules.required && (value === undefined || value === null)) {
181
+ errors.push(`Campo requerido: ${key}`);
182
+ continue;
183
+ }
184
+
185
+ if (value !== undefined && rules.type) {
186
+ const valueType = typeof value;
187
+ if (rules.type === 'array' && !Array.isArray(value)) {
188
+ errors.push(`Campo ${key} debe ser un array`);
189
+ } else if (rules.type !== 'array' && valueType !== rules.type) {
190
+ errors.push(`Campo ${key} debe ser de tipo ${rules.type}, pero es ${valueType}`);
191
+ }
192
+ }
193
+
194
+ if (value !== undefined && rules.validator && typeof rules.validator === 'function') {
195
+ const isValid = rules.validator(value);
196
+ if (!isValid) {
197
+ errors.push(`Campo ${key} no pasó la validación personalizada`);
198
+ }
199
+ }
200
+ }
201
+
202
+ return errors;
203
+ }
204
+
205
+ /**
206
+ * Método para guardar la configuración en un archivo
207
+ * @param {string} filePath - Ruta al archivo donde guardar la configuración
208
+ */
209
+ saveToFile(filePath) {
210
+ try {
211
+ const dir = path.dirname(filePath);
212
+ if (!fs.existsSync(dir)) {
213
+ fs.mkdirSync(dir, { recursive: true });
214
+ }
215
+
216
+ fs.writeFileSync(filePath, JSON.stringify(this.config, null, 2));
217
+ } catch (error) {
218
+ throw new Error(`Error guardando configuración en ${filePath}: ${error.message}`);
219
+ }
220
+ }
221
+ }
222
+
223
+ module.exports = ConfigParser;
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Sistema de logging para el framework API SDK
3
+ * Implementación del componente utils/logger.js
4
+ */
5
+
6
+ class Logger {
7
+ /**
8
+ * Constructor del logger
9
+ * @param {Object} options - Opciones de configuración
10
+ * @param {string} options.level - Nivel de log (debug, info, warn, error)
11
+ * @param {boolean} options.timestamp - Incluir timestamp en los logs
12
+ * @param {string} options.format - Formato de salida (simple, json)
13
+ */
14
+ constructor(options = {}) {
15
+ this.level = options.level || 'info';
16
+ this.timestamp = options.timestamp !== false; // Por defecto true
17
+ this.format = options.format || 'simple';
18
+ this.levels = {
19
+ debug: 0,
20
+ info: 1,
21
+ warn: 2,
22
+ error: 3
23
+ };
24
+ }
25
+
26
+ /**
27
+ * Método para registrar un mensaje de nivel debug
28
+ * @param {...any} args - Argumentos a loggear
29
+ */
30
+ debug(...args) {
31
+ if (this.levels[this.level] <= this.levels.debug) {
32
+ this._log('DEBUG', ...args);
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Método para registrar un mensaje de nivel info
38
+ * @param {...any} args - Argumentos a loggear
39
+ */
40
+ info(...args) {
41
+ if (this.levels[this.level] <= this.levels.info) {
42
+ this._log('INFO', ...args);
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Método para registrar un mensaje de nivel warn
48
+ * @param {...any} args - Argumentos a loggear
49
+ */
50
+ warn(...args) {
51
+ if (this.levels[this.level] <= this.levels.warn) {
52
+ this._log('WARN', ...args);
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Método para registrar un mensaje de nivel error
58
+ * @param {...any} args - Argumentos a loggear
59
+ */
60
+ error(...args) {
61
+ if (this.levels[this.level] <= this.levels.error) {
62
+ this._log('ERROR', ...args);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Método privado para escribir el log
68
+ * @param {string} level - Nivel del log
69
+ * @param {...any} args - Argumentos a loggear
70
+ */
71
+ _log(level, ...args) {
72
+ const timestamp = this.timestamp ? new Date().toISOString() : '';
73
+ const message = args.map(arg => {
74
+ if (typeof arg === 'object') {
75
+ return JSON.stringify(arg, null, 2);
76
+ }
77
+ return String(arg);
78
+ }).join(' ');
79
+
80
+ if (this.format === 'json') {
81
+ const logEntry = {
82
+ timestamp,
83
+ level,
84
+ message
85
+ };
86
+ console.log(JSON.stringify(logEntry));
87
+ } else {
88
+ const prefix = timestamp ? `[${timestamp}] ${level}:` : `${level}:`;
89
+ console.log(prefix, message);
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Método para cambiar el nivel de log
95
+ * @param {string} level - Nuevo nivel de log
96
+ */
97
+ setLevel(level) {
98
+ if (this.levels[level] !== undefined) {
99
+ this.level = level;
100
+ } else {
101
+ throw new Error(`Nivel de log inválido: ${level}. Niveles válidos: ${Object.keys(this.levels).join(', ')}`);
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Método para crear un logger con contexto adicional
107
+ * @param {Object} context - Contexto adicional para los logs
108
+ * @returns {Logger} - Nueva instancia de Logger con contexto
109
+ */
110
+ withContext(context) {
111
+ const logger = new Logger({
112
+ level: this.level,
113
+ timestamp: this.timestamp,
114
+ format: this.format
115
+ });
116
+
117
+ // Sobrescribir el método _log para incluir contexto
118
+ logger._log = (level, ...args) => {
119
+ const timestamp = this.timestamp ? new Date().toISOString() : '';
120
+ const message = args.map(arg => {
121
+ if (typeof arg === 'object') {
122
+ return JSON.stringify(arg, null, 2);
123
+ }
124
+ return String(arg);
125
+ }).join(' ');
126
+
127
+ if (this.format === 'json') {
128
+ const logEntry = {
129
+ timestamp,
130
+ level,
131
+ context,
132
+ message
133
+ };
134
+ console.log(JSON.stringify(logEntry));
135
+ } else {
136
+ const prefix = timestamp ? `[${timestamp}] ${level}:` : `${level}:`;
137
+ console.log(prefix, `[${JSON.stringify(context)}]`, message);
138
+ }
139
+ };
140
+
141
+ return logger;
142
+ }
143
+ }
144
+
145
+ module.exports = { Logger };
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Adaptador de almacenamiento para MariaDB - Implementación Real
3
+ * Componente: lib/utils/mariadbTokenAdapter.js
4
+ */
5
+
6
+ const mariadb = require('mariadb');
7
+
8
+ class MariaDBTokenAdapter {
9
+ /**
10
+ * Constructor del adaptador
11
+ * @param {Object} config - Configuración de conexión a MariaDB
12
+ */
13
+ constructor(config) {
14
+ this.config = {
15
+ host: config.host || 'localhost',
16
+ port: config.port || 3306,
17
+ user: config.user || 'root',
18
+ password: config.password || '',
19
+ database: config.database || 'token_db',
20
+ connectionLimit: config.connectionLimit || 5
21
+ };
22
+
23
+ this.pool = null;
24
+ this.tableName = config.tableName || 'tokens';
25
+ }
26
+
27
+ /**
28
+ * Inicializa la conexión y la tabla de tokens
29
+ */
30
+ async initialize() {
31
+ try {
32
+ // Crear pool de conexiones
33
+ this.pool = mariadb.createPool(this.config);
34
+
35
+ // Crear tabla de tokens si no existe
36
+ await this.initializeTable();
37
+
38
+ console.log('✅ Conexión a MariaDB establecida y tabla de tokens inicializada');
39
+ } catch (error) {
40
+ console.error('❌ Error inicializando MariaDB Token Adapter:', error.message);
41
+ throw error;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Inicializa la tabla de tokens en la base de datos
47
+ */
48
+ async initializeTable() {
49
+ const createTableQuery = `
50
+ CREATE TABLE IF NOT EXISTS ${this.tableName} (
51
+ id INT AUTO_INCREMENT PRIMARY KEY,
52
+ token VARCHAR(500) UNIQUE NOT NULL,
53
+ user_id INT NOT NULL,
54
+ token_type ENUM('access', 'refresh') DEFAULT 'access',
55
+ expires_at TIMESTAMP NOT NULL,
56
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
57
+ revoked BOOLEAN DEFAULT FALSE,
58
+ INDEX idx_user_id (user_id),
59
+ INDEX idx_token (token),
60
+ INDEX idx_expires_at (expires_at)
61
+ )
62
+ `;
63
+
64
+ let conn;
65
+ try {
66
+ conn = await this.pool.getConnection();
67
+ await conn.query(createTableQuery);
68
+ console.log(`✅ Tabla ${this.tableName} inicializada correctamente`);
69
+ } catch (error) {
70
+ console.error('❌ Error inicializando tabla de tokens:', error.message);
71
+ throw error;
72
+ } finally {
73
+ if (conn) conn.release();
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Guarda un token en la base de datos
79
+ * @param {string} token - Token a guardar
80
+ * @param {Object} userData - Datos del usuario
81
+ * @param {string} tokenType - Tipo de token ('access' o 'refresh')
82
+ * @param {Date} expiresAt - Fecha de expiración
83
+ */
84
+ async saveToken(token, userData, tokenType = 'access', expiresAt) {
85
+ const insertQuery = `
86
+ INSERT INTO ${this.tableName} (token, user_id, token_type, expires_at)
87
+ VALUES (?, ?, ?, ?)
88
+ `;
89
+
90
+ let conn;
91
+ try {
92
+ conn = await this.pool.getConnection();
93
+ await conn.query(insertQuery, [
94
+ token,
95
+ userData.userId || userData.id,
96
+ tokenType,
97
+ expiresAt
98
+ ]);
99
+
100
+ console.log(`✅ Token ${tokenType} guardado para usuario ${userData.userId || userData.id}`);
101
+ } catch (error) {
102
+ console.error('❌ Error guardando token:', error.message);
103
+ throw error;
104
+ } finally {
105
+ if (conn) conn.release();
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Verifica si un token existe y es válido
111
+ * @param {string} token - Token a verificar
112
+ * @returns {Object|null} - Datos del token o null si no es válido
113
+ */
114
+ async validateToken(token) {
115
+ const selectQuery = `
116
+ SELECT * FROM ${this.tableName}
117
+ WHERE token = ? AND revoked = FALSE AND expires_at > NOW()
118
+ `;
119
+
120
+ let conn;
121
+ try {
122
+ conn = await this.pool.getConnection();
123
+ const rows = await conn.query(selectQuery, [token]);
124
+ return rows.length > 0 ? rows[0] : null;
125
+ } catch (error) {
126
+ console.error('❌ Error validando token:', error.message);
127
+ return null;
128
+ } finally {
129
+ if (conn) conn.release();
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Revoca un token
135
+ * @param {string} token - Token a revocar
136
+ * @returns {boolean} - True si se revocó exitosamente
137
+ */
138
+ async revokeToken(token) {
139
+ const updateQuery = `
140
+ UPDATE ${this.tableName}
141
+ SET revoked = TRUE
142
+ WHERE token = ?
143
+ `;
144
+
145
+ let conn;
146
+ try {
147
+ conn = await this.pool.getConnection();
148
+ const result = await conn.query(updateQuery, [token]);
149
+ const revoked = result.affectedRows > 0;
150
+
151
+ if (revoked) {
152
+ console.log(`✅ Token revocado: ${token}`);
153
+ } else {
154
+ console.log(`⚠️ Token no encontrado o ya revocado: ${token}`);
155
+ }
156
+
157
+ return revoked;
158
+ } catch (error) {
159
+ console.error('❌ Error revocando token:', error.message);
160
+ return false;
161
+ } finally {
162
+ if (conn) conn.release();
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Obtiene tokens de un usuario
168
+ * @param {number} userId - ID del usuario
169
+ * @returns {Array} - Array de tokens del usuario
170
+ */
171
+ async getUserTokens(userId) {
172
+ const selectQuery = `
173
+ SELECT * FROM ${this.tableName}
174
+ WHERE user_id = ? AND revoked = FALSE AND expires_at > NOW()
175
+ `;
176
+
177
+ let conn;
178
+ try {
179
+ conn = await this.pool.getConnection();
180
+ const rows = await conn.query(selectQuery, [userId]);
181
+ return rows;
182
+ } catch (error) {
183
+ console.error('❌ Error obteniendo tokens de usuario:', error.message);
184
+ return [];
185
+ } finally {
186
+ if (conn) conn.release();
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Limpia tokens expirados
192
+ */
193
+ async cleanupExpiredTokens() {
194
+ const deleteQuery = `
195
+ DELETE FROM ${this.tableName}
196
+ WHERE expires_at <= NOW()
197
+ `;
198
+
199
+ let conn;
200
+ try {
201
+ conn = await this.pool.getConnection();
202
+ const result = await conn.query(deleteQuery);
203
+ const deletedCount = result.affectedRows;
204
+
205
+ console.log(`✅ Eliminados ${deletedCount} tokens expirados`);
206
+ return deletedCount;
207
+ } catch (error) {
208
+ console.error('❌ Error limpiando tokens expirados:', error.message);
209
+ return 0;
210
+ } finally {
211
+ if (conn) conn.release();
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Cierra la conexión al pool
217
+ */
218
+ async close() {
219
+ if (this.pool) {
220
+ await this.pool.end();
221
+ console.log('🔒 Conexión a MariaDB cerrada');
222
+ }
223
+ }
224
+ }
225
+
226
+ module.exports = MariaDBTokenAdapter;