jerkjs 2.1.6 → 2.2.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 (54) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +202 -5
  3. package/index.js +29 -4
  4. package/lib/core/server.js +328 -27
  5. package/lib/loader/routeLoader.js +148 -117
  6. package/lib/middleware/compressor.js +87 -18
  7. package/lib/mvc/GenericAdapter.js +136 -0
  8. package/lib/mvc/MariaDBAdapter.js +315 -0
  9. package/lib/mvc/MemoryAdapter.js +269 -0
  10. package/lib/mvc/ModelControllerExample.js +285 -0
  11. package/lib/mvc/controllerBase.js +60 -0
  12. package/lib/mvc/modelBase.js +383 -0
  13. package/lib/mvc/modelManager.js +284 -0
  14. package/lib/mvc/userModel.js +265 -0
  15. package/lib/mvc/viewEngine.js +32 -1
  16. package/lib/utils/mimeType.js +62 -0
  17. package/package.json +5 -3
  18. package/JERK_FRAMEWORK_DIAGRAM.txt +0 -492
  19. package/JERK_FRAMEWORK_DIAGRAM_MERMAID.mmd +0 -124
  20. package/JERK_FRAMEWORK_DOCUMENTATION.md +0 -527
  21. package/LICENSE +0 -201
  22. package/README_EN.md +0 -230
  23. package/README_PT.md +0 -230
  24. package/docs/ARQUITECTURA_ROUTES.md +0 -140
  25. package/docs/EXTENSION_MANUAL.md +0 -955
  26. package/docs/FIREWALL_MANUAL.md +0 -416
  27. package/docs/HOOK-2.0.md +0 -512
  28. package/docs/HOOKS_REFERENCE_IMPROVED.md +0 -596
  29. package/docs/MANUAL_API_SDK.md +0 -536
  30. package/docs/MARIADB_TOKENS_IMPLEMENTATION.md +0 -110
  31. package/docs/MIDDLEWARE_MANUAL.md +0 -518
  32. package/docs/OAUTH2_GOOGLE_MANUAL.md +0 -405
  33. package/docs/ROUTING_WITHOUT_JSON_GUIDE.md +0 -454
  34. package/docs/frontend-and-sessions.md +0 -353
  35. package/docs/guia_inicio_rapido_jerkjs.md +0 -113
  36. package/examples/examples.arj +0 -0
  37. package/standard/CompressionTestController.js +0 -56
  38. package/standard/HealthController.js +0 -16
  39. package/standard/HomeController.js +0 -12
  40. package/standard/ProductController.js +0 -18
  41. package/standard/README.md +0 -47
  42. package/standard/UserController.js +0 -23
  43. package/standard/package.json +0 -22
  44. package/standard/routes.json +0 -65
  45. package/standard/server.js +0 -140
  46. package/standardA/controllers/AuthController.js +0 -82
  47. package/standardA/controllers/HomeController.js +0 -19
  48. package/standardA/controllers/UserController.js +0 -41
  49. package/standardA/server.js +0 -311
  50. package/standardA/views/auth/dashboard.html +0 -51
  51. package/standardA/views/auth/login.html +0 -47
  52. package/standardA/views/index.html +0 -32
  53. package/standardA/views/users/detail.html +0 -28
  54. package/standardA/views/users/list.html +0 -36
@@ -0,0 +1,265 @@
1
+ /**
2
+ * Modelo de ejemplo para usuarios en el framework JERK
3
+ * Implementación del componente MVC userModel.js
4
+ * Extiende ModelBase para aprovechar todas las funcionalidades
5
+ */
6
+
7
+ const ModelBase = require('./modelBase');
8
+
9
+ class UserModel extends ModelBase {
10
+ /**
11
+ * Constructor del modelo de usuario
12
+ * @param {Object} options - Opciones de configuración del modelo
13
+ */
14
+ constructor(options = {}) {
15
+ super({
16
+ ...options,
17
+ tableName: options.tableName || 'users'
18
+ });
19
+
20
+ // Definir campos del modelo
21
+ this.fields = {
22
+ id: { type: 'integer', primaryKey: true, autoIncrement: true },
23
+ username: { type: 'string', required: true, unique: true },
24
+ email: { type: 'string', required: true, unique: true },
25
+ password: { type: 'string', required: true },
26
+ firstName: { type: 'string' },
27
+ lastName: { type: 'string' },
28
+ createdAt: { type: 'datetime', default: 'CURRENT_TIMESTAMP' },
29
+ updatedAt: { type: 'datetime', default: 'CURRENT_TIMESTAMP' }
30
+ };
31
+ }
32
+
33
+ /**
34
+ * Valida los datos del usuario antes de guardar
35
+ * @param {string} operation - Operación a realizar
36
+ * @param {Object} data - Datos a validar
37
+ * @returns {Object} - Resultado de la validación
38
+ */
39
+ validate(operation, data) {
40
+ const result = { isValid: true, errors: [] };
41
+
42
+ // Validaciones específicas para creación
43
+ if (operation === 'create') {
44
+ if (!data.username) {
45
+ result.isValid = false;
46
+ result.errors.push('Username es requerido');
47
+ }
48
+
49
+ if (!data.email) {
50
+ result.isValid = false;
51
+ result.errors.push('Email es requerido');
52
+ }
53
+
54
+ if (!data.password) {
55
+ result.isValid = false;
56
+ result.errors.push('Password es requerido');
57
+ }
58
+ }
59
+
60
+ // Validaciones para actualización
61
+ if (operation === 'update') {
62
+ // Validar solo los campos que se están actualizando
63
+ if (data.hasOwnProperty('username') && !data.username) {
64
+ result.isValid = false;
65
+ result.errors.push('Username no puede estar vacío si se proporciona');
66
+ }
67
+
68
+ if (data.hasOwnProperty('email') && !data.email) {
69
+ result.isValid = false;
70
+ result.errors.push('Email no puede estar vacío si se proporciona');
71
+ }
72
+
73
+ if (data.hasOwnProperty('password') && !data.password) {
74
+ result.isValid = false;
75
+ result.errors.push('Password no puede estar vacío si se proporciona');
76
+ }
77
+
78
+ // Validar formato de email si se está actualizando
79
+ if (data.email && !this.isValidEmail(data.email)) {
80
+ result.isValid = false;
81
+ result.errors.push('Email no tiene un formato válido');
82
+ }
83
+ }
84
+
85
+ // Validaciones comunes para creación y actualización
86
+ if (operation === 'create' || operation === 'update') {
87
+ // Validar formato de email
88
+ if (data.email && !this.isValidEmail(data.email)) {
89
+ result.isValid = false;
90
+ result.errors.push('Email no tiene un formato válido');
91
+ }
92
+ }
93
+
94
+ // Disparar hook para validación
95
+ if (this.hooks) {
96
+ const validationResult = this.hooks.applyFilters(
97
+ 'user_model_validate',
98
+ result,
99
+ operation,
100
+ data
101
+ );
102
+
103
+ return validationResult;
104
+ }
105
+
106
+ return result;
107
+ }
108
+
109
+ /**
110
+ * Verifica si un email tiene formato válido
111
+ * @param {string} email - Email a validar
112
+ * @returns {boolean} - Verdadero si el email es válido
113
+ */
114
+ isValidEmail(email) {
115
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
116
+ return emailRegex.test(email);
117
+ }
118
+
119
+ /**
120
+ * Busca un usuario por su nombre de usuario
121
+ * @param {string} username - Nombre de usuario a buscar
122
+ * @returns {Promise<Object|null>} - Usuario encontrado o null
123
+ */
124
+ async findByUsername(username) {
125
+ return await this.findOne({ username });
126
+ }
127
+
128
+ /**
129
+ * Busca un usuario por su email
130
+ * @param {string} email - Email a buscar
131
+ * @returns {Promise<Object|null>} - Usuario encontrado o null
132
+ */
133
+ async findByEmail(email) {
134
+ return await this.findOne({ email });
135
+ }
136
+
137
+ /**
138
+ * Crea un nuevo usuario con validación
139
+ * @param {Object} userData - Datos del usuario a crear
140
+ * @returns {Promise<Object>} - Usuario creado
141
+ */
142
+ async createUser(userData) {
143
+ // Validar datos antes de crear
144
+ const validation = this.validate('create', userData);
145
+
146
+ if (!validation.isValid) {
147
+ throw new Error(`Validación fallida: ${validation.errors.join(', ')}`);
148
+ }
149
+
150
+ // Encriptar contraseña antes de guardar
151
+ if (userData.password) {
152
+ userData.password = await this.hashPassword(userData.password);
153
+ }
154
+
155
+ return await this.create(userData);
156
+ }
157
+
158
+ /**
159
+ * Actualiza un usuario con validación
160
+ * @param {Object} conditions - Condiciones para encontrar el usuario
161
+ * @param {Object} userData - Datos del usuario a actualizar
162
+ * @returns {Promise<number>} - Número de filas afectadas
163
+ */
164
+ async updateUser(conditions, userData) {
165
+ // Validar solo los campos que se están actualizando
166
+ const fieldsToUpdate = {};
167
+ for (const [key, value] of Object.entries(userData)) {
168
+ if (value !== undefined && value !== null) {
169
+ fieldsToUpdate[key] = value;
170
+ }
171
+ }
172
+
173
+ // Validar datos antes de actualizar
174
+ const validation = this.validate('update', fieldsToUpdate);
175
+
176
+ if (!validation.isValid) {
177
+ throw new Error(`Validación fallida: ${validation.errors.join(', ')}`);
178
+ }
179
+
180
+ // Encriptar contraseña si se proporciona
181
+ if (fieldsToUpdate.password) {
182
+ fieldsToUpdate.password = await this.hashPassword(fieldsToUpdate.password);
183
+ }
184
+
185
+ return await this.update(conditions, fieldsToUpdate);
186
+ }
187
+
188
+ /**
189
+ * Encripta una contraseña
190
+ * @param {string} password - Contraseña a encriptar
191
+ * @returns {Promise<string>} - Contraseña encriptada
192
+ */
193
+ async hashPassword(password) {
194
+ // Importar bcrypt dinámicamente
195
+ const bcrypt = require('bcrypt');
196
+ const saltRounds = 10;
197
+ return await bcrypt.hash(password, saltRounds);
198
+ }
199
+
200
+ /**
201
+ * Verifica si una contraseña coincide con el hash
202
+ * @param {string} password - Contraseña sin encriptar
203
+ * @param {string} hashedPassword - Contraseña encriptada
204
+ * @returns {Promise<boolean>} - Verdadero si coinciden
205
+ */
206
+ async verifyPassword(password, hashedPassword) {
207
+ // Importar bcrypt dinámicamente
208
+ const bcrypt = require('bcrypt');
209
+ return await bcrypt.compare(password, hashedPassword);
210
+ }
211
+
212
+ /**
213
+ * Obtiene usuarios con información paginada
214
+ * @param {Object} options - Opciones de paginación
215
+ * @param {number} options.page - Página a obtener (por defecto 1)
216
+ * @param {number} options.limit - Límite de resultados por página (por defecto 10)
217
+ * @param {Object} conditions - Condiciones de búsqueda
218
+ * @returns {Promise<Object>} - Resultados con información de paginación
219
+ */
220
+ async getUsersPaginated(options = {}, conditions = {}) {
221
+ const { page = 1, limit = 10 } = options;
222
+ const offset = (page - 1) * limit;
223
+
224
+ // Obtener los usuarios para la página solicitada
225
+ const users = await this.find(conditions, {
226
+ limit,
227
+ offset,
228
+ orderBy: 'createdAt DESC'
229
+ });
230
+
231
+ // Contar el total de usuarios
232
+ const totalCount = await this.count(conditions);
233
+
234
+ // Calcular información de paginación
235
+ const totalPages = Math.ceil(totalCount / limit);
236
+
237
+ return {
238
+ data: users,
239
+ pagination: {
240
+ currentPage: page,
241
+ totalPages,
242
+ totalItems: totalCount,
243
+ itemsPerPage: limit,
244
+ hasNextPage: page < totalPages,
245
+ hasPrevPage: page > 1
246
+ }
247
+ };
248
+ }
249
+
250
+ /**
251
+ * Serializa el modelo de usuario a JSON
252
+ * @returns {Object} - Representación JSON del modelo
253
+ */
254
+ toJSON() {
255
+ const baseJson = super.toJSON();
256
+ return {
257
+ ...baseJson,
258
+ modelName: this.constructor.name,
259
+ tableName: this.tableName,
260
+ fieldCount: Object.keys(this.fields).length
261
+ };
262
+ }
263
+ }
264
+
265
+ module.exports = UserModel;
@@ -215,7 +215,7 @@ class ViewEngine {
215
215
  render(viewName, data = {}, options = {}) {
216
216
  // Obtener la ruta completa de la vista
217
217
  const viewPath = this.getViewPath(viewName);
218
-
218
+
219
219
  // Verificar si la vista existe
220
220
  if (!fs.existsSync(viewPath)) {
221
221
  throw new Error(`Vista no encontrada: ${viewPath}`);
@@ -238,6 +238,37 @@ class ViewEngine {
238
238
  }
239
239
  }
240
240
 
241
+ // Si es una vista de contenido (no un layout), procesarla y usarla como contenido para el layout
242
+ if (options.layout) {
243
+ // Procesar la vista actual con las variables
244
+ const processedViewContent = this.processTemplate(viewContent, data, options);
245
+
246
+ // Cargar el layout
247
+ const layoutPath = this.getViewPath(options.layout);
248
+ if (fs.existsSync(layoutPath)) {
249
+ let layoutContent = fs.readFileSync(layoutPath, 'utf8');
250
+
251
+ // Validar sintaxis del layout si está habilitado
252
+ if (options.validateSyntax !== false) {
253
+ const layoutValidationErrors = ViewEngine.validateTemplate(layoutContent);
254
+ if (layoutValidationErrors.length > 0) {
255
+ this.logger.warn(`Errores de sintaxis en el layout ${options.layout}:`, layoutValidationErrors);
256
+ }
257
+ }
258
+
259
+ // Reemplazar el placeholder {{content}} con el contenido procesado
260
+ layoutContent = layoutContent.replace(/\{\{content\}\}/g, processedViewContent);
261
+
262
+ // Procesar el layout con las variables
263
+ layoutContent = this.processTemplate(layoutContent, data, options);
264
+
265
+ return layoutContent;
266
+ } else {
267
+ // Si no existe el layout, devolver solo la vista procesada
268
+ return this.processTemplate(viewContent, data, options);
269
+ }
270
+ }
271
+
241
272
  // Procesar bloques de inclusión (similar a <?php include ?>)
242
273
  viewContent = this.processIncludes(viewContent, path.dirname(viewPath));
243
274
 
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Utilidad para determinar tipos MIME
3
+ * JERK Framework v2.1 - Sistema de tipos MIME con integración de hooks
4
+ */
5
+
6
+ const mimeTypes = {
7
+ '.html': 'text/html',
8
+ '.htm': 'text/html',
9
+ '.js': 'application/javascript',
10
+ '.css': 'text/css',
11
+ '.json': 'application/json',
12
+ '.xml': 'application/xml',
13
+ '.png': 'image/png',
14
+ '.jpg': 'image/jpeg',
15
+ '.jpeg': 'image/jpeg',
16
+ '.gif': 'image/gif',
17
+ '.svg': 'image/svg+xml',
18
+ '.ico': 'image/x-icon',
19
+ '.woff': 'font/woff',
20
+ '.woff2': 'font/woff2',
21
+ '.ttf': 'font/ttf',
22
+ '.eot': 'application/vnd.ms-fontobject',
23
+ '.pdf': 'application/pdf',
24
+ '.txt': 'text/plain',
25
+ '.csv': 'text/csv',
26
+ '.zip': 'application/zip',
27
+ '.mp3': 'audio/mpeg',
28
+ '.mp4': 'video/mp4',
29
+ '.wav': 'audio/wav',
30
+ '.ogg': 'audio/ogg',
31
+ '.webm': 'video/webm',
32
+ '.webp': 'image/webp',
33
+ '.avif': 'image/avif',
34
+ '.mjs': 'application/javascript',
35
+ '.jsx': 'text/jsx',
36
+ '.ts': 'application/typescript',
37
+ '.tsx': 'text/tsx',
38
+ '.md': 'text/markdown',
39
+ '.yaml': 'text/yaml',
40
+ '.yml': 'text/yaml',
41
+ '.rtf': 'application/rtf',
42
+ '.doc': 'application/msword',
43
+ '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
44
+ '.xls': 'application/vnd.ms-excel',
45
+ '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
46
+ '.ppt': 'application/vnd.ms-powerpoint',
47
+ '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
48
+ };
49
+
50
+ /**
51
+ * Obtiene el tipo MIME para una extensión de archivo
52
+ * @param {string} filename - Nombre del archivo
53
+ * @returns {string} - Tipo MIME correspondiente
54
+ */
55
+ function getMimeType(filename) {
56
+ const extension = '.' + filename.split('.').pop().toLowerCase();
57
+ return mimeTypes[extension] || 'application/octet-stream';
58
+ }
59
+
60
+ module.exports = {
61
+ getMimeType
62
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "jerkjs",
3
- "version": "2.1.6",
4
- "description": "JERK Framework v2.1 - A comprehensive framework for building secure and scalable APIs with frontend support, sessions, and template engine with performance optimizations",
3
+ "version": "2.2.0",
4
+ "description": "JERK Framework v2.2 - A comprehensive framework for building secure and scalable APIs with frontend support, sessions, and template engine with performance optimizations and complete MVC architecture with models",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -24,7 +24,9 @@
24
24
  "waf",
25
25
  "performance",
26
26
  "optimized",
27
- "hooked"
27
+ "hooked",
28
+ "mvc",
29
+ "models"
28
30
  ],
29
31
  "author": "JERK Framework Team / Benjamin Sanhez Cardenas",
30
32
  "license": "Apache-2.0",