jerkjs 2.5.4 → 2.5.8
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.
- package/BENCHMARK_RESULTS.md +60 -0
- package/CHANGELOG.md +43 -0
- package/ESTADISTICAS_RENDIMIENTO.md +106 -0
- package/README.md +142 -423
- package/README_LEGACY.md +513 -0
- package/debug_hook.js +11 -0
- package/doc-2.5/ADMIN_EXTENSION_COMMANDS_MANUAL.md +261 -0
- package/doc-2.5/ADMIN_EXTENSION_HOOK_EXAMPLE.md +28 -0
- package/doc-2.5/ADMIN_EXTENSION_INTEGRATION_MANUAL.md +232 -0
- package/doc-2.5/CACHE_SYSTEM_MAP.md +206 -0
- package/doc-2.5/SESSION_SECURITY_FLAGS.md +174 -0
- package/doc-2.5/an/303/241lisis-completo-jerk-framework.md +213 -0
- package/docs/CACHE_SYSTEM_MAP.md +206 -0
- package/docs/SERVER_OPTIMIZATION_NOTES.md +87 -0
- package/index.js +7 -1
- package/jerk2.5.webp +0 -0
- package/lib/admin/AdminExtension.js +436 -0
- package/lib/admin/ModuleLoader.js +77 -0
- package/lib/admin/config.js +21 -0
- package/lib/admin/modules/CacheModule.js +145 -0
- package/lib/admin/modules/STATS_MODULE_README.md +98 -0
- package/lib/admin/modules/StatsModule.js +140 -0
- package/lib/admin/modules/SystemModule.js +140 -0
- package/lib/admin/modules/TimeModule.js +95 -0
- package/lib/cache/CacheHooks.js +141 -0
- package/lib/core/server.js +199 -46
- package/lib/middleware/session.js +11 -3
- package/lib/mvc/viewEngine.js +26 -1
- package/lib/router/RouteMatcher.js +242 -54
- package/lib/utils/globalStats.js +16 -0
- package/package.json +2 -2
- package/@qaLoadModel/controllers/ProductController.js +0 -143
- package/@qaLoadModel/controllers/UserController.js +0 -143
- package/@qaLoadModel/models/ProductModel.js +0 -41
- package/@qaLoadModel/models/UserModel.js +0 -41
- package/@qaLoadModel/package.json +0 -22
- package/@qaLoadModel/qa_report.md +0 -71
- package/@qaLoadModel/results.md +0 -97
- package/@qaLoadModel/routes.json +0 -58
- package/@qaLoadModel/server.js +0 -43
- package/@qaLoadModel/simple-test.js +0 -96
- package/@qaLoadModel/test-models.js +0 -144
- package/@qaLoadModel/test_endpoints.sh +0 -35
- package/@qaLoadModel/test_final.js +0 -89
- package/@qaLoadModel/views/products/index.html +0 -45
- package/@qaLoadModel/views/products/show.html +0 -27
- package/@qaLoadModel/views/users/index.html +0 -44
- package/@qaLoadModel/views/users/show.html +0 -26
- package/qa/INFORME_QA_JERKJS_ROUTING.md +0 -108
- package/qa/informe_qa_fix_enrutamiento.md +0 -93
- package/qa-app/controllers/homeController.js +0 -9
- package/qa-app/controllers/userController.js +0 -76
- package/qa-app/hooks-config.js +0 -65
- package/qa-app/models/UserModel.js +0 -36
- package/qa-app/package-lock.json +0 -1683
- package/qa-app/package.json +0 -25
- package/qa-app/public/css/style.css +0 -15
- package/qa-app/public/images/logo.png +0 -3
- package/qa-app/public/index.html +0 -15
- package/qa-app/public/js/main.js +0 -7
- package/qa-app/routes/api-routes.json +0 -23
- package/qa-app/routes/page-routes.json +0 -16
- package/qa-app/routes/static-routes.json +0 -20
- package/qa-app/server.js +0 -68
- package/qa-app/views/footer.html +0 -3
- package/qa-app/views/index.html +0 -20
- package/qa-app/views/users.html +0 -20
- package/utils/find_file_path.sh +0 -36
- /package/{doc2.5.3 → doc-2.5}/manual-mvc-completo.md +0 -0
|
@@ -1,31 +1,79 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Componente especializado para la lógica de enrutado
|
|
2
|
+
* Componente especializado para la lógica de enrutado optimizado
|
|
3
3
|
* Implementación del componente router/RouteMatcher.js
|
|
4
|
-
* JERK Framework v2.
|
|
4
|
+
* JERK Framework v2.5.7 - Optimización de rendimiento con índices y pre-filtrado
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
class RouteMatcher {
|
|
8
8
|
/**
|
|
9
|
-
* Constructor del matcher de rutas
|
|
9
|
+
* Constructor del matcher de rutas optimizado
|
|
10
10
|
*/
|
|
11
11
|
constructor() {
|
|
12
12
|
// Cache de expresiones regulares para rutas parametrizadas
|
|
13
13
|
this.routeRegexCache = new Map();
|
|
14
|
+
|
|
15
|
+
// Índices para optimización
|
|
16
|
+
this.exactRoutes = new Map(); // Rutas exactas indexadas por método y path
|
|
17
|
+
this.routeBuckets = {}; // Rutas organizadas por método, número de segmentos y primer segmento
|
|
18
|
+
this.indexesValid = false;
|
|
19
|
+
|
|
20
|
+
// Referencia al sistema de hooks (se establecerá externamente)
|
|
21
|
+
this.hooks = null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Actualiza los índices basados en el conjunto actual de rutas
|
|
26
|
+
* @param {Array} routes - Array de rutas registradas
|
|
27
|
+
*/
|
|
28
|
+
updateIndexes(routes) {
|
|
29
|
+
// Limpiar índices existentes
|
|
30
|
+
this.exactRoutes.clear();
|
|
31
|
+
this.routeBuckets = {};
|
|
32
|
+
|
|
33
|
+
// Reconstruir índices
|
|
34
|
+
for (const route of routes) {
|
|
35
|
+
// Indexar rutas exactas
|
|
36
|
+
const methodPathKey = `${route.method}:${route.path}`;
|
|
37
|
+
this.exactRoutes.set(methodPathKey, route);
|
|
38
|
+
|
|
39
|
+
// Organizar rutas en buckets por método, número de segmentos y primer segmento
|
|
40
|
+
const segments = route.path.split('/').filter(s => s !== '');
|
|
41
|
+
const segmentCount = segments.length;
|
|
42
|
+
const firstSegment = segments.length > 0 ? segments[0] : '';
|
|
43
|
+
|
|
44
|
+
// Inicializar estructura de buckets si no existe
|
|
45
|
+
if (!this.routeBuckets[route.method]) {
|
|
46
|
+
this.routeBuckets[route.method] = {};
|
|
47
|
+
}
|
|
48
|
+
if (!this.routeBuckets[route.method][segmentCount]) {
|
|
49
|
+
this.routeBuckets[route.method][segmentCount] = {};
|
|
50
|
+
}
|
|
51
|
+
if (!this.routeBuckets[route.method][segmentCount][firstSegment]) {
|
|
52
|
+
this.routeBuckets[route.method][segmentCount][firstSegment] = [];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Agregar ruta al bucket correspondiente
|
|
56
|
+
this.routeBuckets[route.method][segmentCount][firstSegment].push(route);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this.indexesValid = true;
|
|
14
60
|
}
|
|
15
61
|
|
|
16
62
|
/**
|
|
17
|
-
* Método para encontrar una ruta coincidente
|
|
63
|
+
* Método para encontrar una ruta coincidente con optimizaciones
|
|
18
64
|
* @param {Array} routes - Array de rutas registradas
|
|
19
65
|
* @param {string} method - Método HTTP
|
|
20
66
|
* @param {string} pathname - Ruta a buscar
|
|
21
67
|
* @returns {Object|null} - Objeto de ruta encontrado o null
|
|
22
68
|
*/
|
|
23
69
|
findRoute(routes, method, pathname) {
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
70
|
+
// Actualizar índices si es necesario
|
|
71
|
+
if (!this.indexesValid || routes.length !== this.getStoredRoutesCount()) {
|
|
72
|
+
this.updateIndexes(routes);
|
|
73
|
+
}
|
|
28
74
|
|
|
75
|
+
// Búsqueda por ruta exacta (más rápida)
|
|
76
|
+
const exactMatch = this.findExactMatch(method, pathname);
|
|
29
77
|
if (exactMatch) {
|
|
30
78
|
return {
|
|
31
79
|
route: exactMatch,
|
|
@@ -33,45 +81,143 @@ class RouteMatcher {
|
|
|
33
81
|
};
|
|
34
82
|
}
|
|
35
83
|
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
84
|
+
// Pre-filtrar rutas candidatas usando índices
|
|
85
|
+
const pathnameSegments = pathname.split('/').filter(s => s !== '');
|
|
86
|
+
const segmentCount = pathnameSegments.length;
|
|
87
|
+
const firstSegment = pathnameSegments.length > 0 ? pathnameSegments[0] : '';
|
|
88
|
+
|
|
89
|
+
// Obtener rutas candidatas basadas en número de segmentos y primer segmento
|
|
90
|
+
const candidateRoutes = this.getCandidateRoutes(method, segmentCount, firstSegment, pathname);
|
|
91
|
+
|
|
92
|
+
// Buscar rutas estáticas que coincidan exactamente
|
|
93
|
+
const staticExactMatch = this.findStaticExactMatch(candidateRoutes, pathname);
|
|
94
|
+
if (staticExactMatch) {
|
|
95
|
+
return {
|
|
96
|
+
route: staticExactMatch,
|
|
97
|
+
params: {}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Buscar rutas parametrizadas entre los candidatos
|
|
102
|
+
const parametrizedMatch = this.findParametrizedMatch(candidateRoutes, pathname);
|
|
103
|
+
if (parametrizedMatch) {
|
|
104
|
+
return parametrizedMatch;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Buscar rutas estáticas (prefijos) entre los candidatos
|
|
108
|
+
const staticPrefixMatch = this.findStaticPrefixMatch(candidateRoutes, pathname);
|
|
109
|
+
if (staticPrefixMatch) {
|
|
110
|
+
return staticPrefixMatch;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Busca rutas exactas usando el índice
|
|
118
|
+
* @param {string} method - Método HTTP
|
|
119
|
+
* @param {string} pathname - Ruta a buscar
|
|
120
|
+
* @returns {Object|null} - Ruta exacta encontrada o null
|
|
121
|
+
*/
|
|
122
|
+
findExactMatch(method, pathname) {
|
|
123
|
+
const methodPathKey = `${method}:${pathname}`;
|
|
124
|
+
return this.exactRoutes.get(methodPathKey) || null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Obtiene rutas candidatas basadas en número de segmentos y primer segmento
|
|
129
|
+
* @param {string} method - Método HTTP
|
|
130
|
+
* @param {number} segmentCount - Número de segmentos de la ruta buscada
|
|
131
|
+
* @param {string} firstSegment - Primer segmento de la ruta buscada
|
|
132
|
+
* @param {string} pathname - Ruta completa buscada
|
|
133
|
+
* @returns {Array} - Array de rutas candidatas
|
|
134
|
+
*/
|
|
135
|
+
getCandidateRoutes(method, segmentCount, firstSegment, pathname) {
|
|
136
|
+
const candidates = [];
|
|
137
|
+
|
|
138
|
+
// Si existe un bucket para este método, número de segmentos y primer segmento
|
|
139
|
+
if (this.routeBuckets[method] &&
|
|
140
|
+
this.routeBuckets[method][segmentCount] &&
|
|
141
|
+
this.routeBuckets[method][segmentCount][firstSegment]) {
|
|
142
|
+
candidates.push(...this.routeBuckets[method][segmentCount][firstSegment]);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// También agregar rutas de otros primeros segmentos para este método y número de segmentos
|
|
146
|
+
// (por si hay rutas parametrizadas que también podrían coincidir)
|
|
147
|
+
if (this.routeBuckets[method] && this.routeBuckets[method][segmentCount]) {
|
|
148
|
+
for (const segment in this.routeBuckets[method][segmentCount]) {
|
|
149
|
+
if (segment !== firstSegment) {
|
|
150
|
+
candidates.push(...this.routeBuckets[method][segmentCount][segment]);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Agregar rutas estáticas que podrían coincidir por prefijo
|
|
156
|
+
if (this.routeBuckets[method]) {
|
|
157
|
+
for (const count in this.routeBuckets[method]) {
|
|
158
|
+
for (const segment in this.routeBuckets[method][count]) {
|
|
159
|
+
const routes = this.routeBuckets[method][count][segment];
|
|
160
|
+
for (const route of routes) {
|
|
161
|
+
if (route.isStatic && pathname.startsWith(route.path)) {
|
|
162
|
+
candidates.push(route);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return candidates;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Busca rutas estáticas con coincidencia exacta entre candidatos
|
|
174
|
+
* @param {Array} candidateRoutes - Rutas candidatas
|
|
175
|
+
* @param {string} pathname - Ruta a buscar
|
|
176
|
+
* @returns {Object|null} - Ruta estática exacta encontrada o null
|
|
177
|
+
*/
|
|
178
|
+
findStaticExactMatch(candidateRoutes, pathname) {
|
|
179
|
+
for (const route of candidateRoutes) {
|
|
42
180
|
if (route.isStatic && route.path === pathname) {
|
|
43
|
-
return
|
|
44
|
-
route: route,
|
|
45
|
-
params: {}
|
|
46
|
-
};
|
|
181
|
+
return route;
|
|
47
182
|
}
|
|
48
183
|
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
49
186
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
params
|
|
65
|
-
|
|
187
|
+
/**
|
|
188
|
+
* Busca rutas parametrizadas entre candidatos
|
|
189
|
+
* @param {Array} candidateRoutes - Rutas candidatas
|
|
190
|
+
* @param {string} pathname - Ruta a buscar
|
|
191
|
+
* @returns {Object|null} - Ruta parametrizada encontrada o null
|
|
192
|
+
*/
|
|
193
|
+
findParametrizedMatch(candidateRoutes, pathname) {
|
|
194
|
+
for (const route of candidateRoutes) {
|
|
195
|
+
if (this.isParametrizedRoute(route.path)) {
|
|
196
|
+
// Convertir ruta parametrizada a expresión regular
|
|
197
|
+
const routeRegex = this.pathToRegex(route.path);
|
|
198
|
+
const match = pathname.match(routeRegex);
|
|
199
|
+
|
|
200
|
+
if (match) {
|
|
201
|
+
const params = this.extractParams(route.path, pathname);
|
|
202
|
+
return {
|
|
203
|
+
route: route,
|
|
204
|
+
params: params
|
|
205
|
+
};
|
|
206
|
+
}
|
|
66
207
|
}
|
|
67
208
|
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
68
211
|
|
|
69
|
-
|
|
212
|
+
/**
|
|
213
|
+
* Busca rutas estáticas por prefijo entre candidatos
|
|
214
|
+
* @param {Array} candidateRoutes - Rutas candidatas
|
|
215
|
+
* @param {string} pathname - Ruta a buscar
|
|
216
|
+
* @returns {Object|null} - Ruta estática por prefijo encontrada o null
|
|
217
|
+
*/
|
|
218
|
+
findStaticPrefixMatch(candidateRoutes, pathname) {
|
|
70
219
|
const staticMatches = [];
|
|
71
|
-
for (const route of
|
|
72
|
-
if (route.method !== method) continue;
|
|
73
|
-
|
|
74
|
-
// Para rutas estáticas, verificar si la ruta solicitada comienza con el prefijo de la ruta estática
|
|
220
|
+
for (const route of candidateRoutes) {
|
|
75
221
|
if (route.isStatic && pathname.startsWith(route.path)) {
|
|
76
222
|
// Verificar que sea exactamente el prefijo o que haya una barra después del prefijo
|
|
77
223
|
const remainingPath = pathname.substring(route.path.length);
|
|
@@ -85,27 +231,40 @@ class RouteMatcher {
|
|
|
85
231
|
}
|
|
86
232
|
}
|
|
87
233
|
|
|
88
|
-
|
|
89
|
-
// (tiene extensión), dar prioridad a la ruta estática más específica
|
|
90
|
-
const hasFileExtension = /\.[^.]+$/.test(pathname);
|
|
91
|
-
|
|
92
|
-
if (hasFileExtension && staticMatches.length > 0) {
|
|
234
|
+
if (staticMatches.length > 0) {
|
|
93
235
|
// Ordenar por especificidad (longitud del prefijo, descendente) y tomar la más específica
|
|
94
236
|
staticMatches.sort((a, b) => b.specificity - a.specificity);
|
|
95
|
-
// Para solicitudes de archivos, dar prioridad a rutas estáticas más específicas
|
|
96
|
-
return staticMatches[0]; // Devolver la coincidencia estática más específica
|
|
97
|
-
} else if (parametrizedMatches.length > 0) {
|
|
98
|
-
// Para solicitudes sin extensión o rutas API, dar prioridad a rutas parametrizadas
|
|
99
|
-
return parametrizedMatches[0]; // Devolver la primera coincidencia parametrizada
|
|
100
|
-
} else if (staticMatches.length > 0) {
|
|
101
|
-
// Si no hay coincidencias parametrizadas, usar la estática más específica
|
|
102
|
-
staticMatches.sort((a, b) => b.specificity - a.specificity);
|
|
103
237
|
return staticMatches[0];
|
|
104
238
|
}
|
|
105
239
|
|
|
106
240
|
return null;
|
|
107
241
|
}
|
|
108
242
|
|
|
243
|
+
/**
|
|
244
|
+
* Determina si una ruta es parametrizada
|
|
245
|
+
* @param {string} path - Ruta a verificar
|
|
246
|
+
* @returns {boolean} - True si la ruta contiene parámetros
|
|
247
|
+
*/
|
|
248
|
+
isParametrizedRoute(path) {
|
|
249
|
+
return path.includes(':');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Obtiene el conteo total de rutas almacenadas en índices
|
|
254
|
+
* @returns {number} - Número total de rutas indexadas
|
|
255
|
+
*/
|
|
256
|
+
getStoredRoutesCount() {
|
|
257
|
+
let count = 0;
|
|
258
|
+
for (const method in this.routeBuckets) {
|
|
259
|
+
for (const segmentCount in this.routeBuckets[method]) {
|
|
260
|
+
for (const firstSegment in this.routeBuckets[method][segmentCount]) {
|
|
261
|
+
count += this.routeBuckets[method][segmentCount][firstSegment].length;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return count;
|
|
266
|
+
}
|
|
267
|
+
|
|
109
268
|
/**
|
|
110
269
|
* Convierte una ruta con parámetros a expresión regular
|
|
111
270
|
* @param {string} path - Ruta con posibles parámetros
|
|
@@ -131,8 +290,20 @@ class RouteMatcher {
|
|
|
131
290
|
const regexPath = escapedPath.replace(/:([a-zA-Z0-9_]+)/g, '([^/]+?)');
|
|
132
291
|
const regex = new RegExp(`^${regexPath}$`);
|
|
133
292
|
|
|
134
|
-
//
|
|
135
|
-
this.
|
|
293
|
+
// Verificar con hook si se debe cachear esta expresión regular
|
|
294
|
+
const shouldCache = this.hooks ?
|
|
295
|
+
this.hooks.applyFilters('should_cache_route_regex', true, path, regex) : true;
|
|
296
|
+
|
|
297
|
+
if (shouldCache) {
|
|
298
|
+
// Almacenar en caché
|
|
299
|
+
this.routeRegexCache.set(path, regex);
|
|
300
|
+
|
|
301
|
+
// Disparar hook después de cachear
|
|
302
|
+
if (this.hooks) {
|
|
303
|
+
this.hooks.doAction('route_regex_cached', path, regex);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
136
307
|
return regex;
|
|
137
308
|
}
|
|
138
309
|
|
|
@@ -169,6 +340,23 @@ class RouteMatcher {
|
|
|
169
340
|
|
|
170
341
|
return params;
|
|
171
342
|
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Limpia el caché de expresiones regulares
|
|
346
|
+
*/
|
|
347
|
+
clearCache() {
|
|
348
|
+
// Disparar hook antes de limpiar el caché
|
|
349
|
+
if (this.hooks) {
|
|
350
|
+
this.hooks.doAction('before_route_regex_cache_clear', this);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
this.routeRegexCache.clear();
|
|
354
|
+
|
|
355
|
+
// Disparar hook después de limpiar el caché
|
|
356
|
+
if (this.hooks) {
|
|
357
|
+
this.hooks.doAction('route_regex_cache_cleared');
|
|
358
|
+
}
|
|
359
|
+
}
|
|
172
360
|
}
|
|
173
361
|
|
|
174
362
|
module.exports = RouteMatcher;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Almacenamiento global de estadísticas para el framework JERK
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Crear un objeto global para almacenar estadísticas
|
|
6
|
+
const globalStats = {
|
|
7
|
+
requestsProcessed: 0,
|
|
8
|
+
responsesSent: 0,
|
|
9
|
+
requestBytes: 0,
|
|
10
|
+
responseBytes: 0,
|
|
11
|
+
routeAccesses: new Map(), // Contador de accesos por ruta
|
|
12
|
+
endpointHits: new Map(), // Contador de hits por endpoint
|
|
13
|
+
lastRequests: [] // Últimas solicitudes para análisis
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
module.exports = { globalStats };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jerkjs",
|
|
3
|
-
"version": "2.5.
|
|
4
|
-
"description": "JERK Framework v2.5.
|
|
3
|
+
"version": "2.5.8",
|
|
4
|
+
"description": "JERK Framework v2.5.8 - A comprehensive framework for building secure and scalable APIs with frontend support, sessions, template engine, integration with qbuilderjs, complete MVC architecture with models, enhanced route loading from directory, improved model loading system, administration extension, and fixed routing issues with static and parametrized routes",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
// controllers/ProductController.js
|
|
2
|
-
const { ControllerBase } = require('../../index.js');
|
|
3
|
-
|
|
4
|
-
class ProductController extends ControllerBase {
|
|
5
|
-
constructor() {
|
|
6
|
-
super();
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
// Método para mostrar la lista de productos
|
|
10
|
-
async index(req, res) {
|
|
11
|
-
try {
|
|
12
|
-
// Obtener el adaptador desde el modelManager en la solicitud
|
|
13
|
-
const adapter = req.modelManager?.getAdapter('memory') || null;
|
|
14
|
-
|
|
15
|
-
// Cargar el modelo de productos con el adaptador
|
|
16
|
-
const productModel = await this.loadModel('ProductModel', {
|
|
17
|
-
adapter: adapter
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
// Obtener todos los productos
|
|
21
|
-
const products = await productModel.getAllProducts();
|
|
22
|
-
|
|
23
|
-
// Renderizar la vista con los datos
|
|
24
|
-
res.render('products/index', {
|
|
25
|
-
title: 'Lista de Productos',
|
|
26
|
-
products: products,
|
|
27
|
-
message: 'Productos disponibles en el sistema'
|
|
28
|
-
});
|
|
29
|
-
} catch (error) {
|
|
30
|
-
console.error('Error en ProductController.index:', error);
|
|
31
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
32
|
-
res.end(JSON.stringify({ error: 'Error interno del servidor' }));
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Método para mostrar un producto específico
|
|
37
|
-
async show(req, res) {
|
|
38
|
-
try {
|
|
39
|
-
// Obtener el adaptador desde el modelManager en la solicitud
|
|
40
|
-
const adapter = req.modelManager?.getAdapter('memory') || null;
|
|
41
|
-
|
|
42
|
-
// Cargar el modelo de productos con el adaptador
|
|
43
|
-
const productModel = await this.loadModel('ProductModel', {
|
|
44
|
-
adapter: adapter
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
// Obtener el ID de los parámetros
|
|
48
|
-
const id = req.params.id;
|
|
49
|
-
|
|
50
|
-
// Obtener el producto específico
|
|
51
|
-
const product = await productModel.getProductById(id);
|
|
52
|
-
|
|
53
|
-
if (!product) {
|
|
54
|
-
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
55
|
-
res.end(JSON.stringify({ error: 'Producto no encontrado' }));
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Renderizar la vista con los datos
|
|
60
|
-
res.render('products/show', {
|
|
61
|
-
title: `Producto #${id}`,
|
|
62
|
-
product: product
|
|
63
|
-
});
|
|
64
|
-
} catch (error) {
|
|
65
|
-
console.error('Error en ProductController.show:', error);
|
|
66
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
67
|
-
res.end(JSON.stringify({ error: 'Error interno del servidor' }));
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Método para crear un nuevo producto
|
|
72
|
-
async create(req, res) {
|
|
73
|
-
try {
|
|
74
|
-
// Obtener el adaptador desde el modelManager en la solicitud
|
|
75
|
-
const adapter = req.modelManager?.getAdapter('memory') || null;
|
|
76
|
-
|
|
77
|
-
// Cargar el modelo de productos con el adaptador
|
|
78
|
-
const productModel = await this.loadModel('ProductModel', {
|
|
79
|
-
adapter: adapter
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// Obtener los datos del cuerpo de la solicitud
|
|
83
|
-
const productData = req.body;
|
|
84
|
-
|
|
85
|
-
// Crear el nuevo producto
|
|
86
|
-
const productId = await productModel.createProduct(productData);
|
|
87
|
-
|
|
88
|
-
// Responder con éxito
|
|
89
|
-
res.writeHead(201, { 'Content-Type': 'application/json' });
|
|
90
|
-
res.end(JSON.stringify({
|
|
91
|
-
success: true,
|
|
92
|
-
id: productId,
|
|
93
|
-
message: 'Producto creado exitosamente'
|
|
94
|
-
}));
|
|
95
|
-
} catch (error) {
|
|
96
|
-
console.error('Error en ProductController.create:', error);
|
|
97
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
98
|
-
res.end(JSON.stringify({ error: 'Error interno del servidor' }));
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Método para buscar productos por categoría
|
|
103
|
-
async searchByCategory(req, res) {
|
|
104
|
-
try {
|
|
105
|
-
// Obtener el adaptador desde el modelManager en la solicitud
|
|
106
|
-
const adapter = req.modelManager?.getAdapter('memory') || null;
|
|
107
|
-
|
|
108
|
-
// Cargar el modelo de productos con el adaptador
|
|
109
|
-
const productModel = await this.loadModel('ProductModel', {
|
|
110
|
-
adapter: adapter
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// Obtener la categoría de los parámetros de consulta
|
|
114
|
-
const category = req.query.category || '';
|
|
115
|
-
|
|
116
|
-
// Buscar productos por categoría
|
|
117
|
-
const products = await productModel.getProductsByCategory(category);
|
|
118
|
-
|
|
119
|
-
// Responder con los resultados
|
|
120
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
121
|
-
res.end(JSON.stringify({
|
|
122
|
-
success: true,
|
|
123
|
-
results: products,
|
|
124
|
-
count: products.length
|
|
125
|
-
}));
|
|
126
|
-
} catch (error) {
|
|
127
|
-
console.error('Error en ProductController.searchByCategory:', error);
|
|
128
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
129
|
-
res.end(JSON.stringify({ error: 'Error interno del servidor' }));
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Crear instancia del controlador
|
|
135
|
-
const productController = new ProductController();
|
|
136
|
-
|
|
137
|
-
// Preservar el contexto 'this' para cada método
|
|
138
|
-
productController.index = productController.index.bind(productController);
|
|
139
|
-
productController.show = productController.show.bind(productController);
|
|
140
|
-
productController.create = productController.create.bind(productController);
|
|
141
|
-
productController.searchByCategory = productController.searchByCategory.bind(productController);
|
|
142
|
-
|
|
143
|
-
module.exports = productController;
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
// controllers/UserController.js
|
|
2
|
-
const { ControllerBase } = require('../../index.js');
|
|
3
|
-
|
|
4
|
-
class UserController extends ControllerBase {
|
|
5
|
-
constructor() {
|
|
6
|
-
super();
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
// Método para mostrar la lista de usuarios
|
|
10
|
-
async index(req, res) {
|
|
11
|
-
try {
|
|
12
|
-
// Obtener el adaptador desde el modelManager en la solicitud
|
|
13
|
-
const adapter = req.modelManager?.getAdapter('memory') || null;
|
|
14
|
-
|
|
15
|
-
// Cargar el modelo de usuarios con el adaptador
|
|
16
|
-
const userModel = await this.loadModel('UserModel', {
|
|
17
|
-
adapter: adapter
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
// Obtener todos los usuarios
|
|
21
|
-
const users = await userModel.getAllUsers();
|
|
22
|
-
|
|
23
|
-
// Renderizar la vista con los datos
|
|
24
|
-
res.render('users/index', {
|
|
25
|
-
title: 'Lista de Usuarios',
|
|
26
|
-
users: users,
|
|
27
|
-
message: 'Usuarios registrados en el sistema'
|
|
28
|
-
});
|
|
29
|
-
} catch (error) {
|
|
30
|
-
console.error('Error en UserController.index:', error);
|
|
31
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
32
|
-
res.end(JSON.stringify({ error: 'Error interno del servidor' }));
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Método para mostrar un usuario específico
|
|
37
|
-
async show(req, res) {
|
|
38
|
-
try {
|
|
39
|
-
// Obtener el adaptador desde el modelManager en la solicitud
|
|
40
|
-
const adapter = req.modelManager?.getAdapter('memory') || null;
|
|
41
|
-
|
|
42
|
-
// Cargar el modelo de usuarios con el adaptador
|
|
43
|
-
const userModel = await this.loadModel('UserModel', {
|
|
44
|
-
adapter: adapter
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
// Obtener el ID de los parámetros
|
|
48
|
-
const id = req.params.id;
|
|
49
|
-
|
|
50
|
-
// Obtener el usuario específico
|
|
51
|
-
const user = await userModel.getUserById(id);
|
|
52
|
-
|
|
53
|
-
if (!user) {
|
|
54
|
-
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
55
|
-
res.end(JSON.stringify({ error: 'Usuario no encontrado' }));
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Renderizar la vista con los datos
|
|
60
|
-
res.render('users/show', {
|
|
61
|
-
title: `Usuario #${id}`,
|
|
62
|
-
user: user
|
|
63
|
-
});
|
|
64
|
-
} catch (error) {
|
|
65
|
-
console.error('Error en UserController.show:', error);
|
|
66
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
67
|
-
res.end(JSON.stringify({ error: 'Error interno del servidor' }));
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Método para crear un nuevo usuario
|
|
72
|
-
async create(req, res) {
|
|
73
|
-
try {
|
|
74
|
-
// Obtener el adaptador desde el modelManager en la solicitud
|
|
75
|
-
const adapter = req.modelManager?.getAdapter('memory') || null;
|
|
76
|
-
|
|
77
|
-
// Cargar el modelo de usuarios con el adaptador
|
|
78
|
-
const userModel = await this.loadModel('UserModel', {
|
|
79
|
-
adapter: adapter
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// Obtener los datos del cuerpo de la solicitud
|
|
83
|
-
const userData = req.body;
|
|
84
|
-
|
|
85
|
-
// Crear el nuevo usuario
|
|
86
|
-
const userId = await userModel.createUser(userData);
|
|
87
|
-
|
|
88
|
-
// Responder con éxito
|
|
89
|
-
res.writeHead(201, { 'Content-Type': 'application/json' });
|
|
90
|
-
res.end(JSON.stringify({
|
|
91
|
-
success: true,
|
|
92
|
-
id: userId,
|
|
93
|
-
message: 'Usuario creado exitosamente'
|
|
94
|
-
}));
|
|
95
|
-
} catch (error) {
|
|
96
|
-
console.error('Error en UserController.create:', error);
|
|
97
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
98
|
-
res.end(JSON.stringify({ error: 'Error interno del servidor' }));
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Método para buscar usuarios por nombre
|
|
103
|
-
async search(req, res) {
|
|
104
|
-
try {
|
|
105
|
-
// Obtener el adaptador desde el modelManager en la solicitud
|
|
106
|
-
const adapter = req.modelManager?.getAdapter('memory') || null;
|
|
107
|
-
|
|
108
|
-
// Cargar el modelo de usuarios con el adaptador
|
|
109
|
-
const userModel = await this.loadModel('UserModel', {
|
|
110
|
-
adapter: adapter
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// Obtener el término de búsqueda de los parámetros de consulta
|
|
114
|
-
const name = req.query.name || '';
|
|
115
|
-
|
|
116
|
-
// Buscar usuarios por nombre
|
|
117
|
-
const users = await userModel.getUsersByName(name);
|
|
118
|
-
|
|
119
|
-
// Responder con los resultados
|
|
120
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
121
|
-
res.end(JSON.stringify({
|
|
122
|
-
success: true,
|
|
123
|
-
results: users,
|
|
124
|
-
count: users.length
|
|
125
|
-
}));
|
|
126
|
-
} catch (error) {
|
|
127
|
-
console.error('Error en UserController.search:', error);
|
|
128
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
129
|
-
res.end(JSON.stringify({ error: 'Error interno del servidor' }));
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Crear instancia del controlador
|
|
135
|
-
const userController = new UserController();
|
|
136
|
-
|
|
137
|
-
// Preservar el contexto 'this' para cada método
|
|
138
|
-
userController.index = userController.index.bind(userController);
|
|
139
|
-
userController.show = userController.show.bind(userController);
|
|
140
|
-
userController.create = userController.create.bind(userController);
|
|
141
|
-
userController.search = userController.search.bind(userController);
|
|
142
|
-
|
|
143
|
-
module.exports = userController;
|