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,301 @@
1
+ /**
2
+ * Middleware de sesión para el framework API SDK
3
+ * Implementación del componente middleware/session.js
4
+ */
5
+
6
+ const crypto = require('crypto');
7
+
8
+ class SessionManager {
9
+ constructor(options = {}) {
10
+ this.sessions = new Map(); // Almacenamiento en memoria de sesiones
11
+ this.cookieName = options.cookieName || 'sessionId';
12
+ this.secret = options.secret || 'default-session-secret-change-me';
13
+ this.timeout = options.timeout || 3600000; // 1 hora por defecto
14
+
15
+ // Obtener instancia de hooks del framework
16
+ try {
17
+ const framework = require('../../index.js');
18
+ this.hooks = framework.hooks;
19
+ } catch (error) {
20
+ // Si no está disponible, crear instancia local
21
+ const HookSystem = require('../core/hooks');
22
+ this.hooks = new HookSystem();
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Genera un ID de sesión seguro
28
+ * @returns {string} - ID de sesión generado
29
+ */
30
+ generateSessionId() {
31
+ return crypto.randomBytes(32).toString('hex');
32
+ }
33
+
34
+ /**
35
+ * Crea una nueva sesión
36
+ * @param {Object} userData - Datos del usuario para almacenar en la sesión
37
+ * @returns {string} - ID de la sesión creada
38
+ */
39
+ createSession(userData) {
40
+ // Permitir que otros módulos modifiquen los datos de la sesión antes de crearla
41
+ const processedUserData = this.hooks.applyFilters('session_create_data', userData);
42
+
43
+ const sessionId = this.generateSessionId();
44
+ const sessionData = {
45
+ id: sessionId,
46
+ data: processedUserData,
47
+ createdAt: Date.now(),
48
+ lastAccessed: Date.now()
49
+ };
50
+
51
+ this.sessions.set(sessionId, sessionData);
52
+
53
+ // Disparar hook después de crear la sesión
54
+ this.hooks.doAction('session_created', sessionId, sessionData);
55
+
56
+ return sessionId;
57
+ }
58
+
59
+ /**
60
+ * Obtiene los datos de una sesión
61
+ * @param {string} sessionId - ID de la sesión
62
+ * @returns {Object|null} - Datos de la sesión o null si no existe
63
+ */
64
+ getSession(sessionId) {
65
+ if (!sessionId) {
66
+ // Disparar hook cuando se intenta obtener una sesión sin ID
67
+ this.hooks.doAction('session_get_no_id');
68
+ return null;
69
+ }
70
+
71
+ // Permitir que otros módulos modifiquen el ID de sesión antes de buscarla
72
+ const processedSessionId = this.hooks.applyFilters('session_get_id', sessionId);
73
+
74
+ const session = this.sessions.get(processedSessionId);
75
+ if (!session) {
76
+ // Disparar hook cuando no se encuentra la sesión
77
+ this.hooks.doAction('session_not_found', processedSessionId);
78
+ return null;
79
+ }
80
+
81
+ // Verificar si la sesión ha expirado
82
+ if (Date.now() - session.lastAccessed > this.timeout) {
83
+ this.destroySession(processedSessionId);
84
+ // Disparar hook cuando la sesión ha expirado
85
+ this.hooks.doAction('session_expired', processedSessionId);
86
+ return null;
87
+ }
88
+
89
+ // Actualizar último acceso
90
+ session.lastAccessed = Date.now();
91
+ this.sessions.set(processedSessionId, session);
92
+
93
+ // Disparar hook después de obtener la sesión
94
+ this.hooks.doAction('session_retrieved', processedSessionId, session.data);
95
+
96
+ return session.data;
97
+ }
98
+
99
+ /**
100
+ * Actualiza los datos de una sesión
101
+ * @param {string} sessionId - ID de la sesión
102
+ * @param {Object} newData - Nuevos datos para la sesión
103
+ * @returns {boolean} - True si la actualización fue exitosa
104
+ */
105
+ updateSession(sessionId, newData) {
106
+ // Permitir que otros módulos modifiquen los nuevos datos antes de actualizar
107
+ const processedNewData = this.hooks.applyFilters('session_update_data', newData, sessionId);
108
+
109
+ const session = this.sessions.get(sessionId);
110
+ if (!session) {
111
+ // Disparar hook cuando se intenta actualizar una sesión inexistente
112
+ this.hooks.doAction('session_update_failed', sessionId, processedNewData);
113
+ return false;
114
+ }
115
+
116
+ session.data = { ...session.data, ...processedNewData };
117
+ session.lastAccessed = Date.now();
118
+ this.sessions.set(sessionId, session);
119
+
120
+ // Disparar hook después de actualizar la sesión
121
+ this.hooks.doAction('session_updated', sessionId, session.data);
122
+
123
+ return true;
124
+ }
125
+
126
+ /**
127
+ * Destruye una sesión
128
+ * @param {string} sessionId - ID de la sesión a destruir
129
+ * @returns {boolean} - True si la destrucción fue exitosa
130
+ */
131
+ destroySession(sessionId) {
132
+ const session = this.sessions.get(sessionId);
133
+ if (!session) {
134
+ // Disparar hook cuando se intenta destruir una sesión inexistente
135
+ this.hooks.doAction('session_destroy_failed', sessionId);
136
+ return false;
137
+ }
138
+
139
+ const result = this.sessions.delete(sessionId);
140
+
141
+ if (result) {
142
+ // Disparar hook después de destruir la sesión
143
+ this.hooks.doAction('session_destroyed', sessionId, session.data);
144
+ }
145
+
146
+ return result;
147
+ }
148
+
149
+ /**
150
+ * Middleware de sesión
151
+ * @returns {Function} - Middleware de sesión
152
+ */
153
+ middleware() {
154
+ return (req, res, next) => {
155
+ // Disparar hook antes de procesar la sesión
156
+ this.hooks.doAction('session_middleware_before', req, res);
157
+
158
+ // Obtener ID de sesión de la cookie
159
+ const cookies = this.parseCookies(req.headers.cookie || '');
160
+ const sessionId = cookies[this.cookieName];
161
+
162
+ // Agregar métodos de sesión a la solicitud
163
+ req.session = {
164
+ id: sessionId,
165
+ data: sessionId ? this.getSession(sessionId) : null,
166
+ create: (userData) => {
167
+ // Permitir que otros módulos modifiquen los datos antes de crear la sesión
168
+ const processedUserData = this.hooks.applyFilters('session_create_user_data', userData, req);
169
+
170
+ const newSessionId = this.createSession(processedUserData);
171
+ req.session.id = newSessionId;
172
+ req.session.data = processedUserData;
173
+
174
+ // Establecer cookie con el ID de sesión
175
+ res.setHeader('Set-Cookie', `${this.cookieName}=${newSessionId}; HttpOnly; Path=/; Max-Age=${this.timeout / 1000}`);
176
+
177
+ // Disparar hook después de crear la sesión
178
+ this.hooks.doAction('session_created_response', req, res, newSessionId);
179
+
180
+ return newSessionId;
181
+ },
182
+ update: (newData) => {
183
+ if (!req.session.id) return false;
184
+
185
+ // Permitir que otros módulos modifiquen los datos antes de actualizar
186
+ const processedNewData = this.hooks.applyFilters('session_update_user_data', newData, req, req.session.id);
187
+
188
+ const result = this.updateSession(req.session.id, processedNewData);
189
+
190
+ // Disparar hook después de actualizar la sesión
191
+ if (result) {
192
+ this.hooks.doAction('session_updated_response', req, res, req.session.id);
193
+ }
194
+
195
+ return result;
196
+ },
197
+ destroy: () => {
198
+ if (!req.session.id) return false;
199
+
200
+ // Disparar hook antes de destruir la sesión
201
+ this.hooks.doAction('session_destroy_before', req, res, req.session.id);
202
+
203
+ // Borrar cookie
204
+ res.setHeader('Set-Cookie', `${this.cookieName}=; HttpOnly; Path=/; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:01 GMT`);
205
+
206
+ const result = this.destroySession(req.session.id);
207
+ req.session.id = null;
208
+ req.session.data = null;
209
+
210
+ // Disparar hook después de destruir la sesión
211
+ if (result) {
212
+ this.hooks.doAction('session_destroyed_response', req, res);
213
+ }
214
+
215
+ return result;
216
+ },
217
+ regenerate: (userData) => {
218
+ if (req.session.id) {
219
+ this.destroySession(req.session.id);
220
+ }
221
+ return req.session.create(userData);
222
+ }
223
+ };
224
+
225
+ // Disparar hook después de procesar la sesión
226
+ this.hooks.doAction('session_middleware_after', req, res);
227
+
228
+ // Continuar con el siguiente middleware
229
+ if (next) {
230
+ next();
231
+ }
232
+ };
233
+ }
234
+
235
+ /**
236
+ * Parsea las cookies de la cabecera
237
+ * @param {string} cookieHeader - Cabecera de cookies
238
+ * @returns {Object} - Objeto con las cookies
239
+ */
240
+ parseCookies(cookieHeader) {
241
+ const cookies = {};
242
+ if (cookieHeader) {
243
+ cookieHeader.split(';').forEach(cookie => {
244
+ const parts = cookie.trim().split('=');
245
+ if (parts.length === 2) {
246
+ cookies[parts[0]] = parts[1];
247
+ }
248
+ });
249
+ }
250
+ return cookies;
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Middleware de autenticación basado en sesión
256
+ * @param {SessionManager} sessionManager - Instancia del administrador de sesiones
257
+ * @param {Object} options - Opciones de autenticación
258
+ * @returns {Function} - Middleware de autenticación de sesión
259
+ */
260
+ function sessionAuth(sessionManager, options = {}) {
261
+ const redirectTo = options.redirectTo || '/login';
262
+ const failureMessage = options.failureMessage || 'Acceso no autorizado. Por favor inicie sesión.';
263
+
264
+ return (req, res, next) => {
265
+ // Disparar hook antes de verificar la autenticación
266
+ sessionManager.hooks.doAction('session_auth_check_before', req, res);
267
+
268
+ // Verificar si hay una sesión activa
269
+ if (!req.session || !req.session.data || !req.session.data.authenticated) {
270
+ // Disparar hook cuando la autenticación falla
271
+ sessionManager.hooks.doAction('session_auth_failed', req, res, redirectTo);
272
+
273
+ // Si es una solicitud AJAX o API, devolver error JSON
274
+ if (req.headers['x-requested-with'] === 'XMLHttpRequest' ||
275
+ req.headers['content-type'] && req.headers['content-type'].includes('application/json')) {
276
+ res.writeHead(401, { 'Content-Type': 'application/json' });
277
+ res.end(JSON.stringify({
278
+ success: false,
279
+ message: failureMessage,
280
+ redirect: redirectTo
281
+ }));
282
+ return;
283
+ } else {
284
+ // Para solicitudes normales, redirigir al login
285
+ res.writeHead(302, { 'Location': redirectTo });
286
+ res.end();
287
+ return;
288
+ }
289
+ }
290
+
291
+ // Disparar hook cuando la autenticación es exitosa
292
+ sessionManager.hooks.doAction('session_auth_success', req, res);
293
+
294
+ // Si hay sesión activa, continuar
295
+ if (next) {
296
+ next();
297
+ }
298
+ };
299
+ }
300
+
301
+ module.exports = { SessionManager, sessionAuth };
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Middleware de validación para el framework API SDK
3
+ * Implementación del componente middleware/validator.js
4
+ */
5
+
6
+ class Validator {
7
+ /**
8
+ * Constructor del validador
9
+ */
10
+ constructor() {
11
+ this.rules = new Map();
12
+ }
13
+
14
+ /**
15
+ * Método para crear un middleware de validación
16
+ * @param {Object} schema - Esquema de validación
17
+ * @returns {Function} - Middleware de validación
18
+ */
19
+ validate(schema) {
20
+ return (req, res, next) => {
21
+ const errors = this.validateSchema(req, schema);
22
+
23
+ if (errors.length > 0) {
24
+ res.writeHead(400, { 'Content-Type': 'application/json' });
25
+ res.end(JSON.stringify({
26
+ error: 'Validación fallida',
27
+ details: errors
28
+ }));
29
+ return;
30
+ }
31
+
32
+ if (next) {
33
+ next();
34
+ }
35
+ };
36
+ }
37
+
38
+ /**
39
+ * Método para validar un esquema contra una solicitud
40
+ * @param {Object} req - Objeto de solicitud HTTP
41
+ * @param {Object} schema - Esquema de validación
42
+ * @returns {Array} - Array de errores de validación
43
+ */
44
+ validateSchema(req, schema) {
45
+ const errors = [];
46
+
47
+ // Validar campos en query
48
+ if (schema.query) {
49
+ for (const [field, rules] of Object.entries(schema.query)) {
50
+ const value = req.query[field];
51
+ const fieldErrors = this.validateField(value, rules, `query.${field}`);
52
+ errors.push(...fieldErrors);
53
+ }
54
+ }
55
+
56
+ // Validar campos en params
57
+ if (schema.params) {
58
+ for (const [field, rules] of Object.entries(schema.params)) {
59
+ const value = req.params[field];
60
+ const fieldErrors = this.validateField(value, rules, `params.${field}`);
61
+ errors.push(...fieldErrors);
62
+ }
63
+ }
64
+
65
+ // Validar campos en body
66
+ if (schema.body) {
67
+ for (const [field, rules] of Object.entries(schema.body)) {
68
+ const value = req.body[field];
69
+ const fieldErrors = this.validateField(value, rules, `body.${field}`);
70
+ errors.push(...fieldErrors);
71
+ }
72
+ }
73
+
74
+ return errors;
75
+ }
76
+
77
+ /**
78
+ * Método para validar un campo individual
79
+ * @param {*} value - Valor a validar
80
+ * @param {Object|Array} rules - Reglas de validación
81
+ * @param {string} fieldName - Nombre del campo para mensajes de error
82
+ * @returns {Array} - Array de errores de validación para este campo
83
+ */
84
+ validateField(value, rules, fieldName) {
85
+ const errors = [];
86
+
87
+ // Si las reglas son un array, procesar cada regla
88
+ const ruleList = Array.isArray(rules) ? rules : [rules];
89
+
90
+ for (const rule of ruleList) {
91
+ // Si la regla es una cadena, interpretar como regla predefinida
92
+ if (typeof rule === 'string') {
93
+ const [ruleName, ...params] = rule.split(':');
94
+ const validationFn = this.getValidationRule(ruleName);
95
+
96
+ if (validationFn) {
97
+ const isValid = validationFn(value, ...params);
98
+ if (!isValid) {
99
+ errors.push(`${fieldName} no cumple con la regla: ${ruleName}`);
100
+ }
101
+ } else {
102
+ errors.push(`Regla de validación desconocida: ${ruleName}`);
103
+ }
104
+ }
105
+ // Si la regla es una función, ejecutarla directamente
106
+ else if (typeof rule === 'function') {
107
+ try {
108
+ const isValid = rule(value);
109
+ if (!isValid) {
110
+ errors.push(`${fieldName} no pasó la validación personalizada`);
111
+ }
112
+ } catch (error) {
113
+ errors.push(`${fieldName} causó un error en la validación: ${error.message}`);
114
+ }
115
+ }
116
+ // Si la regla es un objeto, asumir que es un esquema anidado
117
+ else if (typeof rule === 'object') {
118
+ if (typeof value === 'object' && value !== null) {
119
+ const nestedErrors = this.validateSchema({ body: value }, { body: rule });
120
+ errors.push(...nestedErrors.map(error => `${fieldName}.${error}`));
121
+ } else {
122
+ errors.push(`${fieldName} debe ser un objeto`);
123
+ }
124
+ }
125
+ }
126
+
127
+ return errors;
128
+ }
129
+
130
+ /**
131
+ * Método para obtener una regla de validación por nombre
132
+ * @param {string} ruleName - Nombre de la regla de validación
133
+ * @returns {Function|null} - Función de validación o null si no existe
134
+ */
135
+ getValidationRule(ruleName) {
136
+ switch (ruleName) {
137
+ case 'required':
138
+ return (value) => value !== undefined && value !== null && value !== '';
139
+ case 'string':
140
+ return (value) => typeof value === 'string';
141
+ case 'number':
142
+ return (value) => typeof value === 'number' && !isNaN(value);
143
+ case 'boolean':
144
+ return (value) => typeof value === 'boolean';
145
+ case 'email':
146
+ return (value) => typeof value === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
147
+ case 'minLength':
148
+ return (value, minLength) => typeof value === 'string' && value.length >= parseInt(minLength);
149
+ case 'maxLength':
150
+ return (value, maxLength) => typeof value === 'string' && value.length <= parseInt(maxLength);
151
+ case 'min':
152
+ return (value, min) => typeof value === 'number' && value >= parseFloat(min);
153
+ case 'max':
154
+ return (value, max) => typeof value === 'number' && value <= parseFloat(max);
155
+ case 'regex':
156
+ return (value, pattern) => {
157
+ try {
158
+ const regex = new RegExp(pattern);
159
+ return typeof value === 'string' && regex.test(value);
160
+ } catch (e) {
161
+ return false;
162
+ }
163
+ };
164
+ case 'array':
165
+ return (value) => Array.isArray(value);
166
+ case 'object':
167
+ return (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
168
+ default:
169
+ return null;
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Método para registrar una regla de validación personalizada
175
+ * @param {string} name - Nombre de la regla
176
+ * @param {Function} validationFn - Función de validación
177
+ */
178
+ addRule(name, validationFn) {
179
+ this.rules.set(name, validationFn);
180
+ }
181
+
182
+ /**
183
+ * Método para validar un valor con reglas específicas
184
+ * @param {*} value - Valor a validar
185
+ * @param {Array} rules - Array de reglas de validación
186
+ * @returns {Array} - Array de errores de validación
187
+ */
188
+ validateValue(value, rules) {
189
+ return this.validateField(value, rules, 'value');
190
+ }
191
+ }
192
+
193
+ module.exports = Validator;
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Controlador base para el framework API SDK
3
+ * Implementación del componente MVC controllerBase.js
4
+ * Similar al sistema de controladores de CodeIgniter
5
+ */
6
+
7
+ const ViewEngine = require('./viewEngine');
8
+
9
+ class ControllerBase {
10
+ constructor(options = {}) {
11
+ // Inicializar el motor de vistas
12
+ this.viewEngine = new ViewEngine({
13
+ viewsPath: options.viewsPath || './views',
14
+ defaultExtension: options.defaultExtension || '.html',
15
+ cacheEnabled: options.cacheEnabled
16
+ });
17
+
18
+ // Inicializar variables de datos para la vista
19
+ this.viewData = {};
20
+
21
+ // Referencia al objeto de solicitud y respuesta (cuando esté disponible)
22
+ this.req = null;
23
+ this.res = null;
24
+ }
25
+
26
+ /**
27
+ * Establece variables para pasar a la vista
28
+ * @param {string|Object} key - Nombre de la variable o objeto con múltiples variables
29
+ * @param {*} value - Valor de la variable (si key es string)
30
+ */
31
+ set(key, value) {
32
+ if (typeof key === 'object') {
33
+ // Si se pasa un objeto, fusionar con viewData
34
+ this.viewData = { ...this.viewData, ...key };
35
+ } else {
36
+ // Si se pasa una clave y valor, asignar individualmente
37
+ this.viewData[key] = value;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Renderiza una vista con las variables actuales
43
+ * @param {string} viewName - Nombre de la vista a renderizar
44
+ * @param {Object} additionalData - Datos adicionales para pasar a la vista
45
+ * @param {Object} options - Opciones adicionales
46
+ */
47
+ view(viewName, additionalData = {}, options = {}) {
48
+ // Fusionar datos de la vista con datos adicionales
49
+ const data = { ...this.viewData, ...additionalData };
50
+
51
+ // Renderizar la vista
52
+ const renderedView = this.viewEngine.render(viewName, data, options);
53
+
54
+ return renderedView;
55
+ }
56
+
57
+ /**
58
+ * Renderiza una vista y la envía como respuesta HTTP
59
+ * @param {Object} res - Objeto de respuesta HTTP
60
+ * @param {string} viewName - Nombre de la vista a renderizar
61
+ * @param {Object} additionalData - Datos adicionales para pasar a la vista
62
+ * @param {Object} options - Opciones adicionales
63
+ */
64
+ render(res, viewName, additionalData = {}, options = {}) {
65
+ try {
66
+ // Renderizar la vista
67
+ const renderedView = this.view(viewName, additionalData, options);
68
+
69
+ // Enviar la vista renderizada como respuesta
70
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
71
+ res.end(renderedView);
72
+ } catch (error) {
73
+ console.error('Error renderizando vista:', error);
74
+ res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
75
+ res.end('Error interno del servidor');
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Renderiza una vista parcial (sin layout)
81
+ * @param {string} viewName - Nombre de la vista parcial
82
+ * @param {Object} additionalData - Datos adicionales para pasar a la vista
83
+ * @returns {string} - Vista parcial renderizada
84
+ */
85
+ partial(viewName, additionalData = {}) {
86
+ // Fusionar datos de la vista con datos adicionales
87
+ const data = { ...this.viewData, ...additionalData };
88
+
89
+ // Renderizar la vista parcial
90
+ return this.viewEngine.render(viewName, data);
91
+ }
92
+
93
+ /**
94
+ * Redirecciona a otra URL
95
+ * @param {Object} res - Objeto de respuesta HTTP
96
+ * @param {string} url - URL a la que redireccionar
97
+ */
98
+ redirect(res, url) {
99
+ res.writeHead(302, { 'Location': url });
100
+ res.end();
101
+ }
102
+
103
+ /**
104
+ * Devuelve una respuesta JSON
105
+ * @param {Object} res - Objeto de respuesta HTTP
106
+ * @param {Object} data - Datos a enviar como JSON
107
+ * @param {number} statusCode - Código de estado HTTP
108
+ */
109
+ json(res, data, statusCode = 200) {
110
+ res.writeHead(statusCode, { 'Content-Type': 'application/json; charset=utf-8' });
111
+ res.end(JSON.stringify(data));
112
+ }
113
+
114
+ /**
115
+ * Obtiene un valor de la solicitud (query, body o params)
116
+ * @param {string} key - Clave del valor a obtener
117
+ * @param {*} defaultValue - Valor por defecto si no se encuentra
118
+ * @returns {*} - Valor obtenido o valor por defecto
119
+ */
120
+ input(key, defaultValue = null) {
121
+ if (!this.req) {
122
+ return defaultValue;
123
+ }
124
+
125
+ // Buscar en body, query y params
126
+ if (this.req.body && typeof this.req.body === 'object' && key in this.req.body) {
127
+ return this.req.body[key];
128
+ }
129
+
130
+ if (this.req.query && typeof this.req.query === 'object' && key in this.req.query) {
131
+ return this.req.query[key];
132
+ }
133
+
134
+ if (this.req.params && typeof this.req.params === 'object' && key in this.req.params) {
135
+ return this.req.params[key];
136
+ }
137
+
138
+ return defaultValue;
139
+ }
140
+
141
+ /**
142
+ * Obtiene todos los valores de entrada
143
+ * @returns {Object} - Todos los valores de entrada combinados
144
+ */
145
+ allInput() {
146
+ if (!this.req) {
147
+ return {};
148
+ }
149
+
150
+ return {
151
+ ...this.getBody(),
152
+ ...this.getQuery(),
153
+ ...this.getParams()
154
+ };
155
+ }
156
+
157
+ /**
158
+ * Obtiene los parámetros de consulta (query)
159
+ * @returns {Object} - Parámetros de consulta
160
+ */
161
+ getQuery() {
162
+ return this.req?.query || {};
163
+ }
164
+
165
+ /**
166
+ * Obtiene el cuerpo de la solicitud (body)
167
+ * @returns {Object} - Cuerpo de la solicitud
168
+ */
169
+ getBody() {
170
+ return this.req?.body || {};
171
+ }
172
+
173
+ /**
174
+ * Obtiene los parámetros de ruta (params)
175
+ * @returns {Object} - Parámetros de ruta
176
+ */
177
+ getParams() {
178
+ return this.req?.params || {};
179
+ }
180
+
181
+ /**
182
+ * Establece la solicitud y respuesta actuales
183
+ * @param {Object} req - Objeto de solicitud HTTP
184
+ * @param {Object} res - Objeto de respuesta HTTP
185
+ */
186
+ setRequestResponse(req, res) {
187
+ this.req = req;
188
+ this.res = res;
189
+ }
190
+
191
+ /**
192
+ * Obtiene la instancia del motor de vistas
193
+ * @returns {ViewEngine} - Instancia del motor de vistas
194
+ */
195
+ getViewEngine() {
196
+ return this.viewEngine;
197
+ }
198
+
199
+ /**
200
+ * Limpia las variables de vista
201
+ */
202
+ clearViewData() {
203
+ this.viewData = {};
204
+ }
205
+ }
206
+
207
+ module.exports = ControllerBase;