Compare commits

...

3 Commits

Author SHA1 Message Date
36788f34e8 . 2025-02-05 21:40:59 +01:00
8ead5a62da . 2025-02-04 19:25:10 +01:00
a8c72b1d64 . 2025-02-04 15:58:33 +01:00
49 changed files with 1036 additions and 2201 deletions

View File

@ -6,9 +6,9 @@
"auditLog": ".571b27c4f3dcc376d1f0ca8880ce87cfefd2f30d-audit.json",
"files": [
{
"date": 1738578708264,
"name": "debug-2025-02-03.log",
"hash": "48ca17f819e391cb5ae1909a6ee0fa4d9c8cdb6667ef893547acb004f4a79737"
"date": 1738768744297,
"name": "debug-2025-02-05.log",
"hash": "35182b14bda063a4b734238473f84cfa66a0362a76d8f12f0c20277df81c7256"
}
],
"hashType": "sha256"

View File

@ -6,9 +6,9 @@
"auditLog": ".e6616b1c93d5e50d48b909cd34375b545b447bc6-audit.json",
"files": [
{
"date": 1738578708262,
"name": "error-2025-02-03.log",
"hash": "a21f154f5c386a75eee98a35c2b100da7df1b8002cf99851b90bd12810f1fe8a"
"date": 1738768744292,
"name": "error-2025-02-05.log",
"hash": "c32d976d68382b2ba2ddec8c907c30547ec9fda2bb31180bfdbeb685964810a8"
}
],
"hashType": "sha256"

File diff suppressed because it is too large Load Diff

View File

@ -1,86 +0,0 @@
{"label":"index.ts","level":"error","message":"Mon, 03 Feb 2025 10:31:54 GMT uncaughtException:","metadata":{"0":"S","1":"e","10":"n","11":"o","12":"t","13":" ","14":"r","15":"u","16":"n","17":"n","18":"i","19":"n","2":"r","20":"g","21":".","3":"v","4":"e","5":"r","6":" ","7":"i","8":"s","9":" "},"timestamp":"2025-02-03T10:31:54.031Z"}
{"label":"index.ts","level":"error","message":"Error [ERR_SERVER_NOT_RUNNING]: Server is not running.\n at Server.close (node:net:2356:12)\n at Object.onceWrapper (node:events:638:28)\n at Server.emit (node:events:536:35)\n at emitCloseNT (node:net:2416:8)\n at process.processTicksAndRejections (node:internal/process/task_queues:89:21)","metadata":{},"timestamp":"2025-02-03T10:31:54.034Z"}
{"label":"index.ts","level":"error","message":"Mon, 03 Feb 2025 10:33:04 GMT uncaughtException:","metadata":{"0":"S","1":"e","10":"n","11":"o","12":"t","13":" ","14":"r","15":"u","16":"n","17":"n","18":"i","19":"n","2":"r","20":"g","21":".","3":"v","4":"e","5":"r","6":" ","7":"i","8":"s","9":" "},"timestamp":"2025-02-03T10:33:04.894Z"}
{"label":"index.ts","level":"error","message":"Error [ERR_SERVER_NOT_RUNNING]: Server is not running.\n at Server.close (node:net:2356:12)\n at Object.onceWrapper (node:events:638:28)\n at Server.emit (node:events:536:35)\n at emitCloseNT (node:net:2416:8)\n at process.processTicksAndRejections (node:internal/process/task_queues:89:21)","metadata":{},"timestamp":"2025-02-03T10:33:04.896Z"}
{"label":"index.ts","level":"error","message":"Mon, 03 Feb 2025 10:33:49 GMT uncaughtException:","metadata":{"0":"S","1":"e","10":"n","11":"o","12":"t","13":" ","14":"r","15":"u","16":"n","17":"n","18":"i","19":"n","2":"r","20":"g","21":".","3":"v","4":"e","5":"r","6":" ","7":"i","8":"s","9":" "},"timestamp":"2025-02-03T10:33:49.232Z"}
{"label":"index.ts","level":"error","message":"Error [ERR_SERVER_NOT_RUNNING]: Server is not running.\n at Server.close (node:net:2356:12)\n at Object.onceWrapper (node:events:638:28)\n at Server.emit (node:events:536:35)\n at emitCloseNT (node:net:2416:8)\n at process.processTicksAndRejections (node:internal/process/task_queues:89:21)","metadata":{},"timestamp":"2025-02-03T10:33:49.233Z"}
{"label":"index.ts","level":"error","message":"Mon, 03 Feb 2025 10:37:14 GMT uncaughtException:","metadata":{"0":"S","1":"e","10":"n","11":"o","12":"t","13":" ","14":"r","15":"u","16":"n","17":"n","18":"i","19":"n","2":"r","20":"g","21":".","3":"v","4":"e","5":"r","6":" ","7":"i","8":"s","9":" "},"timestamp":"2025-02-03T10:37:14.756Z"}
{"label":"index.ts","level":"error","message":"Error [ERR_SERVER_NOT_RUNNING]: Server is not running.\n at Server.close (node:net:2356:12)\n at Object.onceWrapper (node:events:638:28)\n at Server.emit (node:events:536:35)\n at emitCloseNT (node:net:2416:8)\n at process.processTicksAndRejections (node:internal/process/task_queues:89:21)","metadata":{},"timestamp":"2025-02-03T10:37:14.758Z"}
{"label":"index.ts","level":"error","message":"Unhandled Rejection at:","metadata":{"0":"r","1":"e","2":"a","3":"s","4":"o","5":"n","6":":","code":"ERR_SERVER_NOT_RUNNING"},"timestamp":"2025-02-03T10:47:26.394Z"}
{"label":"index.ts","level":"error","message":"Unhandled Rejection at:","metadata":{"0":"r","1":"e","2":"a","3":"s","4":"o","5":"n","6":":","code":"ERR_SERVER_NOT_RUNNING"},"timestamp":"2025-02-03T10:47:46.769Z"}
{"label":"index.ts","level":"error","message":"Unhandled Rejection at:","metadata":{"0":"r","1":"e","2":"a","3":"s","4":"o","5":"n","6":":","code":"ERR_SERVER_NOT_RUNNING"},"timestamp":"2025-02-03T10:56:00.612Z"}
{"label":"index.ts","level":"error","message":"Unhandled Rejection at:","metadata":{"0":"r","1":"e","2":"a","3":"s","4":"o","5":"n","6":":","name":"SequelizeDatabaseError","original":{"code":"ER_NO_SUCH_TABLE","errno":1146,"fatal":false,"name":"SqlError","sql":"SELECT `id`, `username`, `email`, `password`, `roles`, `isActive`, `created_at`, `updated_at`, `deleted_at` FROM `users` AS `AuthUserModel` WHERE (`AuthUserModel`.`deleted_at` IS NULL AND `AuthUserModel`.`id` = 'email');","sqlMessage":"Table 'uecko_erp.users' doesn't exist","sqlState":"42S02"},"parameters":{},"parent":{"code":"ER_NO_SUCH_TABLE","errno":1146,"fatal":false,"name":"SqlError","sql":"SELECT `id`, `username`, `email`, `password`, `roles`, `isActive`, `created_at`, `updated_at`, `deleted_at` FROM `users` AS `AuthUserModel` WHERE (`AuthUserModel`.`deleted_at` IS NULL AND `AuthUserModel`.`id` = 'email');","sqlMessage":"Table 'uecko_erp.users' doesn't exist","sqlState":"42S02"},"sql":"SELECT `id`, `username`, `email`, `password`, `roles`, `isActive`, `created_at`, `updated_at`, `deleted_at` FROM `users` AS `AuthUserModel` WHERE (`AuthUserModel`.`deleted_at` IS NULL AND `AuthUserModel`.`id` = 'email');"},"timestamp":"2025-02-03T11:08:06.568Z"}
{"label":"index.ts","level":"error","message":"Unhandled Rejection at:","metadata":{"0":"r","1":"e","2":"a","3":"s","4":"o","5":"n","6":":","name":"SequelizeDatabaseError","original":{"code":"ER_NO_SUCH_TABLE","errno":1146,"fatal":false,"name":"SqlError","sql":"SELECT `id`, `username`, `email`, `password`, `roles`, `isActive`, `created_at`, `updated_at`, `deleted_at` FROM `users` AS `AuthUserModel` WHERE (`AuthUserModel`.`deleted_at` IS NULL AND `AuthUserModel`.`id` = 'email');","sqlMessage":"Table 'uecko_erp.users' doesn't exist","sqlState":"42S02"},"parameters":{},"parent":{"code":"ER_NO_SUCH_TABLE","errno":1146,"fatal":false,"name":"SqlError","sql":"SELECT `id`, `username`, `email`, `password`, `roles`, `isActive`, `created_at`, `updated_at`, `deleted_at` FROM `users` AS `AuthUserModel` WHERE (`AuthUserModel`.`deleted_at` IS NULL AND `AuthUserModel`.`id` = 'email');","sqlMessage":"Table 'uecko_erp.users' doesn't exist","sqlState":"42S02"},"sql":"SELECT `id`, `username`, `email`, `password`, `roles`, `isActive`, `created_at`, `updated_at`, `deleted_at` FROM `users` AS `AuthUserModel` WHERE (`AuthUserModel`.`deleted_at` IS NULL AND `AuthUserModel`.`id` = 'email');"},"timestamp":"2025-02-03T11:08:30.574Z"}
{"label":"index.ts","level":"error","message":"Unhandled Rejection at:","metadata":{"0":"r","1":"e","2":"a","3":"s","4":"o","5":"n","6":":","code":"ERR_SERVER_NOT_RUNNING"},"timestamp":"2025-02-03T11:09:05.310Z"}
{"label":"index.ts","level":"error","message":"Unhandled Rejection at:","metadata":{"0":"r","1":"e","2":"a","3":"s","4":"o","5":"n","6":":","name":"SequelizeDatabaseError","original":{"code":"ER_NO_SUCH_TABLE","errno":1146,"fatal":false,"name":"SqlError","sql":"SELECT `id`, `username`, `email`, `password`, `roles`, `isActive`, `created_at`, `updated_at`, `deleted_at` FROM `users` AS `AuthUserModel` WHERE (`AuthUserModel`.`deleted_at` IS NULL AND `AuthUserModel`.`id` = 'email');","sqlMessage":"Table 'uecko_erp.users' doesn't exist","sqlState":"42S02"},"parameters":{},"parent":{"code":"ER_NO_SUCH_TABLE","errno":1146,"fatal":false,"name":"SqlError","sql":"SELECT `id`, `username`, `email`, `password`, `roles`, `isActive`, `created_at`, `updated_at`, `deleted_at` FROM `users` AS `AuthUserModel` WHERE (`AuthUserModel`.`deleted_at` IS NULL AND `AuthUserModel`.`id` = 'email');","sqlMessage":"Table 'uecko_erp.users' doesn't exist","sqlState":"42S02"},"sql":"SELECT `id`, `username`, `email`, `password`, `roles`, `isActive`, `created_at`, `updated_at`, `deleted_at` FROM `users` AS `AuthUserModel` WHERE (`AuthUserModel`.`deleted_at` IS NULL AND `AuthUserModel`.`id` = 'email');"},"timestamp":"2025-02-03T11:14:03.193Z"}
{"label":"index.ts","level":"error","message":"Unhandled Rejection at:","metadata":{"0":"r","1":"e","2":"a","3":"s","4":"o","5":"n","6":":","name":"SequelizeDatabaseError","original":{"code":"ER_NO_SUCH_TABLE","errno":1146,"fatal":false,"name":"SqlError","sql":"SELECT `id`, `username`, `email`, `password`, `roles`, `isActive`, `created_at`, `updated_at`, `deleted_at` FROM `users` AS `AuthUserModel` WHERE (`AuthUserModel`.`deleted_at` IS NULL AND `AuthUserModel`.`id` = 'email');","sqlMessage":"Table 'uecko_erp.users' doesn't exist","sqlState":"42S02"},"parameters":{},"parent":{"code":"ER_NO_SUCH_TABLE","errno":1146,"fatal":false,"name":"SqlError","sql":"SELECT `id`, `username`, `email`, `password`, `roles`, `isActive`, `created_at`, `updated_at`, `deleted_at` FROM `users` AS `AuthUserModel` WHERE (`AuthUserModel`.`deleted_at` IS NULL AND `AuthUserModel`.`id` = 'email');","sqlMessage":"Table 'uecko_erp.users' doesn't exist","sqlState":"42S02"},"sql":"SELECT `id`, `username`, `email`, `password`, `roles`, `isActive`, `created_at`, `updated_at`, `deleted_at` FROM `users` AS `AuthUserModel` WHERE (`AuthUserModel`.`deleted_at` IS NULL AND `AuthUserModel`.`id` = 'email');"},"timestamp":"2025-02-03T11:20:47.049Z"}
{"label":"index.ts","level":"error","message":"Could not close connections in time, forcefully shutting down","metadata":{},"timestamp":"2025-02-03T11:21:20.528Z"}
{"label":"index.ts","level":"error","message":"Unhandled Rejection at:","metadata":{"0":"r","1":"e","2":"a","3":"s","4":"o","5":"n","6":":","name":"SequelizeDatabaseError","original":{"code":"ER_NO_SUCH_TABLE","errno":1146,"fatal":false,"name":"SqlError","sql":"SELECT `id`, `username`, `email`, `password`, `roles`, `isActive`, `created_at`, `updated_at`, `deleted_at` FROM `users` AS `AuthUserModel` WHERE (`AuthUserModel`.`deleted_at` IS NULL AND `AuthUserModel`.`id` = 'email');","sqlMessage":"Table 'uecko_erp.users' doesn't exist","sqlState":"42S02"},"parameters":{},"parent":{"code":"ER_NO_SUCH_TABLE","errno":1146,"fatal":false,"name":"SqlError","sql":"SELECT `id`, `username`, `email`, `password`, `roles`, `isActive`, `created_at`, `updated_at`, `deleted_at` FROM `users` AS `AuthUserModel` WHERE (`AuthUserModel`.`deleted_at` IS NULL AND `AuthUserModel`.`id` = 'email');","sqlMessage":"Table 'uecko_erp.users' doesn't exist","sqlState":"42S02"},"sql":"SELECT `id`, `username`, `email`, `password`, `roles`, `isActive`, `created_at`, `updated_at`, `deleted_at` FROM `users` AS `AuthUserModel` WHERE (`AuthUserModel`.`deleted_at` IS NULL AND `AuthUserModel`.`id` = 'email');"},"timestamp":"2025-02-03T11:22:22.098Z"}
{"label":"index.ts","level":"error","message":"Could not close connections in time, forcefully shutting down","metadata":{},"timestamp":"2025-02-03T11:29:22.891Z"}
{"label":"index.ts","level":"error","message":"Could not close connections in time, forcefully shutting down","metadata":{},"timestamp":"2025-02-03T11:30:14.987Z"}
{"label":"index.ts","level":"error","message":"Unhandled Rejection at:","metadata":{"0":"r","1":"e","2":"a","3":"s","4":"o","5":"n","6":":","name":"SequelizeDatabaseError","original":{"code":"ER_NO_SUCH_TABLE","errno":1146,"fatal":false,"name":"SqlError","sql":"SELECT `id`, `username`, `email`, `password`, `roles`, `isActive`, `created_at`, `updated_at`, `deleted_at` FROM `users` AS `AuthUserModel` WHERE (`AuthUserModel`.`deleted_at` IS NULL AND `AuthUserModel`.`id` = 'email');","sqlMessage":"Table 'uecko_erp.users' doesn't exist","sqlState":"42S02"},"parameters":{},"parent":{"code":"ER_NO_SUCH_TABLE","errno":1146,"fatal":false,"name":"SqlError","sql":"SELECT `id`, `username`, `email`, `password`, `roles`, `isActive`, `created_at`, `updated_at`, `deleted_at` FROM `users` AS `AuthUserModel` WHERE (`AuthUserModel`.`deleted_at` IS NULL AND `AuthUserModel`.`id` = 'email');","sqlMessage":"Table 'uecko_erp.users' doesn't exist","sqlState":"42S02"},"sql":"SELECT `id`, `username`, `email`, `password`, `roles`, `isActive`, `created_at`, `updated_at`, `deleted_at` FROM `users` AS `AuthUserModel` WHERE (`AuthUserModel`.`deleted_at` IS NULL AND `AuthUserModel`.`id` = 'email');"},"timestamp":"2025-02-03T11:39:04.414Z"}
{"label":"index.ts","level":"error","message":"Unhandled Rejection at:","metadata":{"0":"r","1":"e","2":"a","3":"s","4":"o","5":"n","6":":"},"timestamp":"2025-02-03T13:05:42.332Z"}
{"label":"index.ts","level":"error","message":"Database error: value.join is not a function","metadata":{},"timestamp":"2025-02-03T15:59:05.262Z"}
{"label":"index.ts","level":"error","message":"[500] Internal Server Error: Unexpected database error","metadata":{},"timestamp":"2025-02-03T15:59:05.277Z"}
{"label":"index.ts","level":"error","message":"Database error: value.join is not a function","metadata":{},"timestamp":"2025-02-03T15:59:35.470Z"}
{"label":"index.ts","level":"error","message":"[500] Internal Server Error: Unexpected database error","metadata":{},"timestamp":"2025-02-03T15:59:35.473Z"}
{"label":"index.ts","level":"error","message":"Database error: value.join is not a function","metadata":{},"timestamp":"2025-02-03T15:59:48.809Z"}
{"label":"index.ts","level":"error","message":"[500] Internal Server Error: Unexpected database error","metadata":{},"timestamp":"2025-02-03T15:59:48.811Z"}
{"label":"index.ts","level":"error","message":"Database error: value.join is not a function","metadata":{},"timestamp":"2025-02-03T16:01:28.918Z"}
{"label":"index.ts","level":"error","message":"[500] Internal Server Error: Unexpected database error","metadata":{},"timestamp":"2025-02-03T16:01:28.932Z"}
{"label":"index.ts","level":"error","message":"Database error: value.join is not a function","metadata":{},"timestamp":"2025-02-03T16:02:07.671Z"}
{"label":"index.ts","level":"error","message":"[500] Internal Server Error: Unexpected database error","metadata":{},"timestamp":"2025-02-03T16:02:07.678Z"}
{"label":"index.ts","level":"error","message":"Unhandled Rejection at:","metadata":{"0":"r","1":"e","2":"a","3":"s","4":"o","5":"n","6":":"},"timestamp":"2025-02-03T16:05:54.782Z"}
{"label":"index.ts","level":"error","message":"Database error: Validation error","metadata":{},"timestamp":"2025-02-03T16:06:09.336Z"}
{"label":"index.ts","level":"error","message":"[409] Conflict: User with this email already exists","metadata":{},"timestamp":"2025-02-03T16:06:09.345Z"}
{"label":"index.ts","level":"error","message":"Database error: Validation error","metadata":{},"timestamp":"2025-02-03T16:29:22.183Z"}
{"label":"index.ts","level":"error","message":"[409] Conflict: User with this email already exists","metadata":{},"timestamp":"2025-02-03T16:29:22.193Z"}
{"label":"index.ts","level":"error","message":"Database error: Validation error","metadata":{},"timestamp":"2025-02-03T16:29:25.843Z"}
{"label":"index.ts","level":"error","message":"[500] Internal Server Error: Unexpected database error","metadata":{},"timestamp":"2025-02-03T16:29:25.848Z"}
{"label":"index.ts","level":"error","message":"Database error: Validation error","metadata":{},"timestamp":"2025-02-03T16:29:58.465Z"}
{"label":"index.ts","level":"error","message":"[409] Conflict: User with this email already exists","metadata":{},"timestamp":"2025-02-03T16:29:58.471Z"}
{"label":"index.ts","level":"error","message":"Database error: Validation error","metadata":{},"timestamp":"2025-02-03T16:30:58.651Z"}
{"label":"index.ts","level":"error","message":"[409] Conflict: User with this email already exists","metadata":{},"timestamp":"2025-02-03T16:30:58.659Z"}
{"label":"index.ts","level":"error","message":"Unhandled Rejection at:","metadata":{"0":"r","1":"e","2":"a","3":"s","4":"o","5":"n","6":":"},"timestamp":"2025-02-03T16:31:11.297Z"}
{"label":"index.ts","level":"error","message":"Database error: Validation error","metadata":{},"timestamp":"2025-02-03T16:31:39.443Z"}
{"label":"index.ts","level":"error","message":"[409] Conflict: User with this email already exists","metadata":{},"timestamp":"2025-02-03T16:31:39.446Z"}
{"label":"index.ts","level":"error","message":"Unhandled Rejection at:","metadata":{"0":"r","1":"e","2":"a","3":"s","4":"o","5":"n","6":":"},"timestamp":"2025-02-03T16:42:21.737Z"}
{"label":"index.ts","level":"error","message":"Database error: Validation error","metadata":{},"timestamp":"2025-02-03T16:53:04.645Z"}
{"label":"index.ts","level":"error","message":"[409] Conflict: User with this email already exists","metadata":{},"timestamp":"2025-02-03T16:53:04.652Z"}
{"label":"index.ts","level":"error","message":"Unhandled Rejection at:","metadata":{"0":"r","1":"e","2":"a","3":"s","4":"o","5":"n","6":":"},"timestamp":"2025-02-03T16:53:24.919Z"}
{"label":"index.ts","level":"error","message":"Unhandled Rejection at:","metadata":{"0":"r","1":"e","2":"a","3":"s","4":"o","5":"n","6":":"},"timestamp":"2025-02-03T16:53:59.783Z"}
{"label":"index.ts","level":"error","message":"Database error: Validation error","metadata":{},"timestamp":"2025-02-03T17:53:46.420Z"}
{"label":"index.ts","level":"error","message":"[409] Conflict: User with this email already exists","metadata":{},"timestamp":"2025-02-03T17:53:46.434Z"}
{"label":"index.ts","level":"error","message":"Unhandled API error: Validation Error","metadata":{},"timestamp":"2025-02-03T18:09:57.217Z"}
{"label":"index.ts","level":"error","message":"Unhandled API error: Validation Error","metadata":{},"timestamp":"2025-02-03T18:10:42.558Z"}
{"label":"index.ts","level":"error","message":"Unhandled API error: Unknown authentication strategy \"jwt\"","metadata":{},"timestamp":"2025-02-03T18:34:31.991Z"}
{"label":"index.ts","level":"error","message":"Unhandled API error: Unknown authentication strategy \"jwt\"","metadata":{},"timestamp":"2025-02-03T18:36:02.667Z"}
{"label":"index.ts","level":"error","message":"Unhandled API error: Unknown authentication strategy \"jwt\"","metadata":{},"timestamp":"2025-02-03T18:44:04.627Z"}
{"label":"index.ts","level":"error","message":"Unhandled API error: Unknown authentication strategy \"jwt\"","metadata":{},"timestamp":"2025-02-03T18:44:24.719Z"}
{"label":"index.ts","level":"error","message":"Unhandled API error: Unknown authentication strategy \"jwt\"","metadata":{},"timestamp":"2025-02-03T18:46:29.064Z"}
{"label":"index.ts","level":"error","message":"Unhandled API error: Unknown authentication strategy \"jwt\"","metadata":{},"timestamp":"2025-02-03T18:46:40.967Z"}
{"label":"index.ts","level":"error","message":"Database error: Validation error","metadata":{},"timestamp":"2025-02-03T19:07:24.350Z"}
{"label":"index.ts","level":"error","message":"[409] Conflict: User with this email already exists","metadata":{},"timestamp":"2025-02-03T19:07:24.352Z"}
{"label":"index.ts","level":"error","message":"Unhandled API error: Unknown authentication strategy \"local-jwt\"","metadata":{},"timestamp":"2025-02-03T19:09:15.070Z"}
{"label":"index.ts","level":"error","message":"Unhandled API error: Unknown authentication strategy \"jwt\"","metadata":{},"timestamp":"2025-02-03T19:09:23.879Z"}
{"label":"index.ts","level":"error","message":"❌ Error synchronizing database: (conn:635, no: 1069, SQLState: 42000) Too many keys specified; max 64 keys allowed\nsql: ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE; - parameters:[]","metadata":{"name":"SequelizeDatabaseError","original":{"code":"ER_TOO_MANY_KEYS","errno":1069,"fatal":false,"name":"SqlError","sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","sqlMessage":"Too many keys specified; max 64 keys allowed","sqlState":"42000"},"parameters":{},"parent":{"code":"ER_TOO_MANY_KEYS","errno":1069,"fatal":false,"name":"SqlError","sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","sqlMessage":"Too many keys specified; max 64 keys allowed","sqlState":"42000"},"sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","stack":"Error: \n at Query.run (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/dialects/mariadb/query.js:47:25)\n at /home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/sequelize.js:650:28\n at processTicksAndRejections (node:internal/process/task_queues:105:5)\n at Function.sync (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/model.js:1408:11)\n at Sequelize.sync (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/sequelize.js:825:9)\n at registerModels (/home/rodax/Documentos/uecko-erp/apps/server/src/config/register-models.ts:50:7)\n at connectToDatabase (/home/rodax/Documentos/uecko-erp/apps/server/src/config/database.ts:42:5)\n at /home/rodax/Documentos/uecko-erp/apps/server/src/index.ts:109:5"},"timestamp":"2025-02-03T19:24:23.645Z"}
{"label":"index.ts","level":"error","message":"Unhandled API error: Unknown authentication strategy \"jwt\"","metadata":{},"timestamp":"2025-02-03T19:45:07.427Z"}
{"label":"index.ts","level":"error","message":"💥 Unhandled API error: Unknown authentication strategy \"jwt\"","metadata":{},"timestamp":"2025-02-03T19:46:17.959Z"}
{"label":"index.ts","level":"error","message":"❌ Error synchronizing database: (conn:823, no: 1069, SQLState: 42000) Too many keys specified; max 64 keys allowed\nsql: ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE; - parameters:[]","metadata":{"name":"SequelizeDatabaseError","original":{"code":"ER_TOO_MANY_KEYS","errno":1069,"fatal":false,"name":"SqlError","sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","sqlMessage":"Too many keys specified; max 64 keys allowed","sqlState":"42000"},"parameters":{},"parent":{"code":"ER_TOO_MANY_KEYS","errno":1069,"fatal":false,"name":"SqlError","sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","sqlMessage":"Too many keys specified; max 64 keys allowed","sqlState":"42000"},"sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","stack":"Error: \n at Query.run (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/dialects/mariadb/query.js:47:25)\n at /home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/sequelize.js:650:28\n at processTicksAndRejections (node:internal/process/task_queues:105:5)\n at Function.sync (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/model.js:1408:11)\n at Sequelize.sync (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/sequelize.js:825:9)\n at registerModels (/home/rodax/Documentos/uecko-erp/apps/server/src/config/register-models.ts:57:7)\n at connectToDatabase (/home/rodax/Documentos/uecko-erp/apps/server/src/config/database.ts:42:5)\n at /home/rodax/Documentos/uecko-erp/apps/server/src/index.ts:115:5"},"timestamp":"2025-02-03T21:43:21.049Z"}
{"label":"index.ts","level":"error","message":"❌ Error synchronizing database: (conn:827, no: 1069, SQLState: 42000) Too many keys specified; max 64 keys allowed\nsql: ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE; - parameters:[]","metadata":{"name":"SequelizeDatabaseError","original":{"code":"ER_TOO_MANY_KEYS","errno":1069,"fatal":false,"name":"SqlError","sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","sqlMessage":"Too many keys specified; max 64 keys allowed","sqlState":"42000"},"parameters":{},"parent":{"code":"ER_TOO_MANY_KEYS","errno":1069,"fatal":false,"name":"SqlError","sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","sqlMessage":"Too many keys specified; max 64 keys allowed","sqlState":"42000"},"sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","stack":"Error: \n at Query.run (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/dialects/mariadb/query.js:47:25)\n at /home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/sequelize.js:650:28\n at processTicksAndRejections (node:internal/process/task_queues:105:5)\n at Function.sync (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/model.js:1408:11)\n at Sequelize.sync (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/sequelize.js:825:9)\n at registerModels (/home/rodax/Documentos/uecko-erp/apps/server/src/config/register-models.ts:57:7)\n at connectToDatabase (/home/rodax/Documentos/uecko-erp/apps/server/src/config/database.ts:42:5)\n at /home/rodax/Documentos/uecko-erp/apps/server/src/index.ts:115:5"},"timestamp":"2025-02-03T21:43:23.751Z"}
{"label":"index.ts","level":"error","message":"❌ Error synchronizing database: (conn:829, no: 1069, SQLState: 42000) Too many keys specified; max 64 keys allowed\nsql: ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE; - parameters:[]","metadata":{"name":"SequelizeDatabaseError","original":{"code":"ER_TOO_MANY_KEYS","errno":1069,"fatal":false,"name":"SqlError","sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","sqlMessage":"Too many keys specified; max 64 keys allowed","sqlState":"42000"},"parameters":{},"parent":{"code":"ER_TOO_MANY_KEYS","errno":1069,"fatal":false,"name":"SqlError","sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","sqlMessage":"Too many keys specified; max 64 keys allowed","sqlState":"42000"},"sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","stack":"Error: \n at Query.run (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/dialects/mariadb/query.js:47:25)\n at /home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/sequelize.js:650:28\n at processTicksAndRejections (node:internal/process/task_queues:105:5)\n at Function.sync (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/model.js:1408:11)\n at Sequelize.sync (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/sequelize.js:825:9)\n at registerModels (/home/rodax/Documentos/uecko-erp/apps/server/src/config/register-models.ts:57:7)\n at connectToDatabase (/home/rodax/Documentos/uecko-erp/apps/server/src/config/database.ts:42:5)\n at /home/rodax/Documentos/uecko-erp/apps/server/src/index.ts:115:5"},"timestamp":"2025-02-03T21:43:37.528Z"}
{"label":"index.ts","level":"error","message":"❌ Error synchronizing database: (conn:833, no: 1069, SQLState: 42000) Too many keys specified; max 64 keys allowed\nsql: ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE; - parameters:[]","metadata":{"name":"SequelizeDatabaseError","original":{"code":"ER_TOO_MANY_KEYS","errno":1069,"fatal":false,"name":"SqlError","sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","sqlMessage":"Too many keys specified; max 64 keys allowed","sqlState":"42000"},"parameters":{},"parent":{"code":"ER_TOO_MANY_KEYS","errno":1069,"fatal":false,"name":"SqlError","sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","sqlMessage":"Too many keys specified; max 64 keys allowed","sqlState":"42000"},"sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","stack":"Error: \n at Query.run (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/dialects/mariadb/query.js:47:25)\n at /home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/sequelize.js:650:28\n at processTicksAndRejections (node:internal/process/task_queues:105:5)\n at Function.sync (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/model.js:1408:11)\n at Sequelize.sync (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/sequelize.js:825:9)\n at registerModels (/home/rodax/Documentos/uecko-erp/apps/server/src/config/register-models.ts:57:7)\n at connectToDatabase (/home/rodax/Documentos/uecko-erp/apps/server/src/config/database.ts:42:5)\n at /home/rodax/Documentos/uecko-erp/apps/server/src/index.ts:115:5"},"timestamp":"2025-02-03T21:43:44.126Z"}
{"label":"index.ts","level":"error","message":"❌ Error synchronizing database: (conn:835, no: 1069, SQLState: 42000) Too many keys specified; max 64 keys allowed\nsql: ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE; - parameters:[]","metadata":{"name":"SequelizeDatabaseError","original":{"code":"ER_TOO_MANY_KEYS","errno":1069,"fatal":false,"name":"SqlError","sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","sqlMessage":"Too many keys specified; max 64 keys allowed","sqlState":"42000"},"parameters":{},"parent":{"code":"ER_TOO_MANY_KEYS","errno":1069,"fatal":false,"name":"SqlError","sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","sqlMessage":"Too many keys specified; max 64 keys allowed","sqlState":"42000"},"sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","stack":"Error: \n at Query.run (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/dialects/mariadb/query.js:47:25)\n at /home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/sequelize.js:650:28\n at processTicksAndRejections (node:internal/process/task_queues:105:5)\n at Function.sync (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/model.js:1408:11)\n at Sequelize.sync (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/sequelize.js:825:9)\n at registerModels (/home/rodax/Documentos/uecko-erp/apps/server/src/config/register-models.ts:57:7)\n at connectToDatabase (/home/rodax/Documentos/uecko-erp/apps/server/src/config/database.ts:42:5)\n at /home/rodax/Documentos/uecko-erp/apps/server/src/index.ts:115:5"},"timestamp":"2025-02-03T21:44:09.809Z"}
{"label":"index.ts","level":"error","message":"❌ Error synchronizing database: (conn:839, no: 1069, SQLState: 42000) Too many keys specified; max 64 keys allowed\nsql: ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE; - parameters:[]","metadata":{"name":"SequelizeDatabaseError","original":{"code":"ER_TOO_MANY_KEYS","errno":1069,"fatal":false,"name":"SqlError","sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","sqlMessage":"Too many keys specified; max 64 keys allowed","sqlState":"42000"},"parameters":{},"parent":{"code":"ER_TOO_MANY_KEYS","errno":1069,"fatal":false,"name":"SqlError","sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","sqlMessage":"Too many keys specified; max 64 keys allowed","sqlState":"42000"},"sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","stack":"Error: \n at Query.run (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/dialects/mariadb/query.js:47:25)\n at /home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/sequelize.js:650:28\n at processTicksAndRejections (node:internal/process/task_queues:105:5)\n at Function.sync (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/model.js:1408:11)\n at Sequelize.sync (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/sequelize.js:825:9)\n at registerModels (/home/rodax/Documentos/uecko-erp/apps/server/src/config/register-models.ts:57:7)\n at connectToDatabase (/home/rodax/Documentos/uecko-erp/apps/server/src/config/database.ts:42:5)\n at /home/rodax/Documentos/uecko-erp/apps/server/src/index.ts:115:5"},"timestamp":"2025-02-03T21:46:33.069Z"}
{"label":"index.ts","level":"error","message":"❌ Error synchronizing database: (conn:842, no: 1069, SQLState: 42000) Too many keys specified; max 64 keys allowed\nsql: ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE; - parameters:[]","metadata":{"name":"SequelizeDatabaseError","original":{"code":"ER_TOO_MANY_KEYS","errno":1069,"fatal":false,"name":"SqlError","sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","sqlMessage":"Too many keys specified; max 64 keys allowed","sqlState":"42000"},"parameters":{},"parent":{"code":"ER_TOO_MANY_KEYS","errno":1069,"fatal":false,"name":"SqlError","sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","sqlMessage":"Too many keys specified; max 64 keys allowed","sqlState":"42000"},"sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","stack":"Error: \n at Query.run (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/dialects/mariadb/query.js:47:25)\n at /home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/sequelize.js:650:28\n at processTicksAndRejections (node:internal/process/task_queues:105:5)\n at Function.sync (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/model.js:1408:11)\n at Sequelize.sync (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/sequelize.js:825:9)\n at registerModels (/home/rodax/Documentos/uecko-erp/apps/server/src/config/register-models.ts:57:7)\n at connectToDatabase (/home/rodax/Documentos/uecko-erp/apps/server/src/config/database.ts:43:5)\n at /home/rodax/Documentos/uecko-erp/apps/server/src/index.ts:115:5"},"timestamp":"2025-02-03T21:46:50.035Z"}
{"label":"index.ts","level":"error","message":"❌ Error synchronizing database: (conn:845, no: 1069, SQLState: 42000) Too many keys specified; max 64 keys allowed\nsql: ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE; - parameters:[]","metadata":{"name":"SequelizeDatabaseError","original":{"code":"ER_TOO_MANY_KEYS","errno":1069,"fatal":false,"name":"SqlError","sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","sqlMessage":"Too many keys specified; max 64 keys allowed","sqlState":"42000"},"parameters":{},"parent":{"code":"ER_TOO_MANY_KEYS","errno":1069,"fatal":false,"name":"SqlError","sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","sqlMessage":"Too many keys specified; max 64 keys allowed","sqlState":"42000"},"sql":"ALTER TABLE `users` CHANGE `email` `email` VARCHAR(255) NOT NULL UNIQUE;","stack":"Error: \n at Query.run (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/dialects/mariadb/query.js:47:25)\n at /home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/sequelize.js:650:28\n at processTicksAndRejections (node:internal/process/task_queues:105:5)\n at Function.sync (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/model.js:1408:11)\n at Sequelize.sync (/home/rodax/Documentos/uecko-erp/node_modules/.pnpm/sequelize@6.37.5_mariadb@3.4.0_mysql2@3.12.0/node_modules/sequelize/src/sequelize.js:825:9)\n at registerModels (/home/rodax/Documentos/uecko-erp/apps/server/src/config/register-models.ts:57:7)\n at connectToDatabase (/home/rodax/Documentos/uecko-erp/apps/server/src/config/database.ts:43:5)\n at /home/rodax/Documentos/uecko-erp/apps/server/src/index.ts:115:5"},"timestamp":"2025-02-03T21:47:08.690Z"}
{"label":"index.ts","level":"error","message":"❌ Unhandled API error: createLoginController is not defined","metadata":{},"timestamp":"2025-02-03T21:50:39.274Z"}
{"label":"index.ts","level":"error","message":"❌ Unhandled API error: createLoginController is not defined","metadata":{},"timestamp":"2025-02-03T21:52:51.178Z"}
{"label":"index.ts","level":"error","message":"❌ Unhandled API error: createLoginController is not defined","metadata":{},"timestamp":"2025-02-03T21:53:12.086Z"}
{"label":"index.ts","level":"error","message":"[503] Service Unavailable: Invalid email or password","metadata":{},"timestamp":"2025-02-03T21:53:25.527Z"}
{"label":"index.ts","level":"error","message":"[503] Service Unavailable: Invalid email or password","metadata":{},"timestamp":"2025-02-03T21:53:36.301Z"}
{"label":"index.ts","level":"error","message":"[503] Service Unavailable: Invalid email or password","metadata":{},"timestamp":"2025-02-03T21:53:39.351Z"}
{"label":"index.ts","level":"error","message":"[401] Unauthorized: Invalid email or password","metadata":{},"timestamp":"2025-02-03T21:53:53.504Z"}
{"label":"index.ts","level":"error","message":"Database error: Validation error","metadata":{},"timestamp":"2025-02-03T21:54:31.584Z"}
{"label":"index.ts","level":"error","message":"[409] Conflict: User with this email already exists","metadata":{},"timestamp":"2025-02-03T21:54:31.587Z"}
{"label":"index.ts","level":"error","message":"[401] Unauthorized: Invalid email or password","metadata":{},"timestamp":"2025-02-03T21:54:42.181Z"}

View File

@ -1,5 +1,6 @@
import { logger } from "@common/infrastructure/logger";
import { globalErrorHandler } from "@common/presentation";
import { initializePassportAuthProvide } from "@contexts/auth/infraestructure";
import dotenv from "dotenv";
import express, { Application } from "express";
import helmet from "helmet";
@ -24,7 +25,10 @@ export function createApp(): Application {
app.use(responseTime()); // set up the response-time middleware
// Inicializar Passport
//app.use((req, res, next) => createPassportAuthProvider());
app.use((req, res, next) => {
initializePassportAuthProvide();
next();
});
app.use((req, _, next) => {
logger.info(`▶️ Incoming request ${req.method} to ${req.path}`);

View File

@ -46,19 +46,27 @@ export class Result<T, E extends Error = Error> {
}
get data(): T {
if (!this._isSuccess) {
throw new Error("Cannot get value data from a failed result.");
}
return this._data as T;
return this.getData();
}
get error(): E {
return this.getError();
}
getError(): E {
if (this._isSuccess) {
throw new Error("Cannot get error from a successful result.");
}
return this._error as E;
}
getData(): T {
if (!this._isSuccess) {
throw new Error("Cannot get value data from a failed result.");
}
return this._data as T;
}
/**
* 🔹 `getOrElse(defaultValue: T): T`
* Si el `Result` es un `ok`, devuelve `data`, de lo contrario, devuelve `defaultValue`.

View File

@ -1,35 +1,54 @@
import { UniqueID } from "./value-objects/unique-id";
import { UniqueID } from "./unique-id";
describe("UniqueID Value Object", () => {
it("should generate a new UUID using generateNewID()", () => {
const result = UniqueID.generateNewID();
// Mock UUID generation to ensure predictable tests
jest.mock("uuid", () => ({ v4: () => "123e4567-e89b-12d3-a456-426614174000" }));
describe("UniqueID", () => {
test("should create a UniqueID with a valid UUID", () => {
const id = "123e4567-e89b-12d3-a456-426614174000";
const result = UniqueID.create(id);
expect(result.isSuccess).toBe(true);
expect(result.data.getValue()).toMatch(
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
);
expect(result.data?.isDefined()).toBe(true);
});
it("should return an error for an invalid UUID", () => {
test("should fail to create UniqueID with an invalid UUID", () => {
const result = UniqueID.create("invalid-uuid");
expect(result.isSuccess).toBe(true);
expect(result.error.message).toBe("Invalid UUID format");
expect(result.isFailure).toBe(true);
});
it("should create a valid UniqueID from an existing UUID", () => {
const validUUID = "550e8400-e29b-41d4-a716-446655440000";
const result = UniqueID.create(validUUID);
test("should create an undefined UniqueID when id is undefined and generateOnEmpty is false", () => {
const result = UniqueID.create(undefined, false);
expect(result.isSuccess).toBe(true);
expect(result.data.getValue()).toBe(validUUID);
expect(result.data?.isDefined()).toBe(false);
});
it("should correctly convert UniqueID to string", () => {
const validUUID = "550e8400-e29b-41d4-a716-446655440000";
const result = UniqueID.create(validUUID);
test("should generate a new UUID when id is undefined and generateOnEmpty is true", () => {
const result = UniqueID.create(undefined, true);
expect(result.isSuccess).toBe(true);
expect(result.data.toString()).toBe(validUUID);
expect(result.data?.isDefined()).toBe(true);
});
test("should fail when id is null", () => {
const result = UniqueID.create(null as any);
expect(result.isFailure).toBe(true);
});
test("should create a UniqueID when id is an empty string and generateOnEmpty is true", () => {
const result = UniqueID.create(" ", true);
expect(result.isSuccess).toBe(true);
expect(result.data?.isDefined()).toBe(true);
});
test("should create an undefined UniqueID when id is an empty string and generateOnEmpty is false", () => {
const result = UniqueID.create(" ", false);
expect(result.isSuccess).toBe(true);
expect(result.data?.isDefined()).toBe(false);
});
});

View File

@ -3,17 +3,27 @@ import { z } from "zod";
import { Result } from "../result";
import { ValueObject } from "./value-object";
const UUIDSchema = z.string().uuid({ message: "Invalid UUID format" });
export const UNDEFINED_ID = undefined;
export class UniqueID extends ValueObject<string | undefined> {
protected readonly _hasId!: boolean;
protected constructor(id?: string) {
super(id);
this._hasId = id != UNDEFINED_ID;
}
export class UniqueID extends ValueObject<string> {
static create(id?: string, generateOnEmpty: boolean = false): Result<UniqueID, Error> {
if (!id) {
return generateOnEmpty
? UniqueID.generateNewID()
: Result.fail(new Error("ID is null or empty"));
if (id === null) {
return Result.fail(new Error("ID cannot be null"));
}
const result = UniqueID.validate(id.trim());
const trimmedId = id?.trim();
if (!trimmedId) {
return generateOnEmpty ? UniqueID.generateNewID() : Result.ok(new UniqueID(UNDEFINED_ID));
}
const result = UniqueID.validate(trimmedId);
return result.success
? Result.ok(new UniqueID(result.data))
@ -32,4 +42,12 @@ export class UniqueID extends ValueObject<string> {
static generateNewID(): Result<UniqueID, never> {
return Result.ok(new UniqueID(uuidv4()));
}
static generateUndefinedID(): Result<UniqueID, never> {
return Result.ok(new UniqueID(UNDEFINED_ID));
}
isDefined(): boolean {
return this._hasId;
}
}

View File

@ -5,8 +5,6 @@ export abstract class ValueObject<T> {
protected constructor(value: T) {
this._value = typeof value === "object" && value !== null ? Object.freeze(value) : value;
Object.freeze(this);
}
equals(other: ValueObject<T>): boolean {

View File

@ -15,6 +15,36 @@ export abstract class SequelizeRepository<T> implements IAggregateRootRepository
});
}
protected async _getBy(
model: ModelDefined<any, any>,
field: string,
value: any,
params: any = {},
transaction?: Transaction
): Promise<any> {
const where: { [key: string]: any } = {};
where[field] = value;
return model.findOne({
where,
transaction,
...params,
});
}
protected async _getById(
model: ModelDefined<any, any>,
id: UniqueID | string,
params: any = {},
transaction?: Transaction
): Promise<any> {
return model.findByPk(id.toString(), {
transaction,
...params,
});
}
protected async _exists(
model: ModelDefined<any, any>,
field: string,

View File

@ -51,8 +51,9 @@ export abstract class ExpressController {
return ExpressController.errorResponse(
new ApiError({
status: 401,
title: "Unauthorized",
detail: message ?? "You are not authorized to access this resource.",
title: httpStatus["401"],
name: httpStatus["401_NAME"],
detail: message ?? httpStatus["401_MESSAGE"],
}),
this.res
);
@ -108,8 +109,9 @@ export abstract class ExpressController {
return ExpressController.errorResponse(
new ApiError({
status: 422,
title: "Invalid Input",
detail: message,
title: httpStatus["422"],
name: httpStatus["422_NAME"],
detail: message ?? httpStatus["422_MESSAGE"],
errors,
}),
this.res

View File

@ -2,10 +2,10 @@ import { AuthenticatedUser } from "../domain";
export interface IAuthProvider {
/* JWT Strategy */
generateAccessToken(payload: any): string;
generateRefreshToken(payload: any): string;
verifyToken(token: string): any;
generateAccessToken(payload: object): string;
generateRefreshToken(payload: object): string;
verifyToken(token: string): Promise<AuthenticatedUser | null>;
/* LocalStrategy */
verifyUser(email: string, password: string): Promise<AuthenticatedUser | null>;
//_verifyUser(email: string, password: string): Promise<AuthenticatedUser | null>;
}

View File

@ -1,15 +1,27 @@
import { Result } from "@common/domain";
import { AuthenticatedUser, EmailAddress, PasswordHash, Username } from "../domain";
import { Result, UniqueID } from "@common/domain";
import { AuthenticatedUser, EmailAddress, HashPassword, PlainPassword, Username } from "../domain";
export interface IAuthService {
registerUser(params: {
username: Username;
email: EmailAddress;
passwordHash: PasswordHash;
hashPassword: HashPassword;
}): Promise<Result<AuthenticatedUser, Error>>;
loginUser(params: {
email: EmailAddress;
passwordHash: PasswordHash;
}): Promise<Result<AuthenticatedUser, Error>>;
plainPassword: PlainPassword;
tabId: UniqueID;
}): Promise<
Result<
{
user: AuthenticatedUser;
tokens: {
accessToken: string;
refreshToken: string;
};
},
Error
>
>;
}

View File

@ -3,24 +3,29 @@ import { ITransactionManager } from "@common/infrastructure/database";
import {
AuthenticatedUser,
EmailAddress,
HashPassword,
IAuthenticatedUserRepository,
PasswordHash,
TabContext,
Username,
} from "@contexts/auth/domain";
} from "../domain";
import { ITabContextRepository } from "../domain/repositories/tab-context-repository.interface";
import { IAuthProvider } from "./auth-provider.interface";
import { IAuthService } from "./auth-service.interface";
export class AuthService implements IAuthService {
private readonly _respository!: IAuthenticatedUserRepository;
private readonly _userRepo!: IAuthenticatedUserRepository;
private readonly _tabContactRepo!: ITabContextRepository;
private readonly _transactionManager!: ITransactionManager;
private readonly _authProvider: IAuthProvider;
constructor(
repository: IAuthenticatedUserRepository,
userRepo: IAuthenticatedUserRepository,
tabContextRepo: ITabContextRepository,
transactionManager: ITransactionManager,
authProvider: IAuthProvider
) {
this._respository = repository;
this._userRepo = userRepo;
this._tabContactRepo = tabContextRepo;
this._transactionManager = transactionManager;
this._authProvider = authProvider;
}
@ -32,29 +37,25 @@ export class AuthService implements IAuthService {
async registerUser(params: {
username: Username;
email: EmailAddress;
passwordHash: PasswordHash;
hashPassword: HashPassword;
}): Promise<Result<AuthenticatedUser, Error>> {
try {
return await this._transactionManager.complete(async (transaction) => {
const { username, email, passwordHash } = params;
const { username, email, hashPassword } = params;
// Verificar si el usuario ya existe
const userExists = await this._respository.findUserByEmail(email, transaction);
const userExists = await this._userRepo.userExists(email, transaction);
if (userExists.isSuccess && userExists.data) {
return Result.fail(new Error("Email is already registered"));
}
if (userExists.isFailure) {
return Result.fail(userExists.error);
}
const newUserId = UniqueID.generateNewID().data;
const userOrError = AuthenticatedUser.create(
{
username,
email,
passwordHash,
hashPassword,
roles: ["USER"],
},
newUserId
@ -64,7 +65,7 @@ export class AuthService implements IAuthService {
return Result.fail(userOrError.error);
}
const createdResult = await this._respository.createUser(userOrError.data, transaction);
const createdResult = await this._userRepo.createUser(userOrError.data, transaction);
if (createdResult.isFailure) {
return Result.fail(createdResult.error);
@ -83,79 +84,79 @@ export class AuthService implements IAuthService {
*/
async loginUser(params: {
email: EmailAddress;
passwordHash: PasswordHash;
}): Promise<Result<AuthenticatedUser, Error>> {
plainPassword: HashPassword;
tabId: UniqueID;
}): Promise<
Result<
{
user: AuthenticatedUser;
tokens: {
accessToken: string;
refreshToken: string;
};
},
Error
>
> {
try {
return await this._transactionManager.complete(async (transaction) => {
const { email, passwordHash } = params;
const { email, plainPassword, tabId } = params;
// Verificar que el tab ID está definido
if (!tabId.isDefined()) {
return Result.fail(new Error("Invalid tab id"));
}
// 🔹 Verificar si el usuario existe en la base de datos
const userResult = await this._respository.findUserByEmail(email, transaction);
const userResult = await this._userRepo.getUserByEmail(email, transaction);
if (userResult.isFailure) {
return Result.fail(new Error("Invalid email or password"));
}
const user = userResult.data;
if (!user) {
return Result.fail(new Error("Invalid email or password"));
}
// 🔹 Verificar que la contraseña sea correcta
const isValidPassword = await user.comparePassword(passwordHash);
const isValidPassword = await user.verifyPassword(plainPassword);
if (!isValidPassword) {
return Result.fail(new Error("Invalid email or password"));
}
// Registrar o actualizar el contexto de ese tab ID
const contextOrError = TabContext.create({
userId: user.id,
tabId: tabId,
companyId: UniqueID.generateUndefinedID().data,
branchId: UniqueID.generateUndefinedID().data,
});
if (contextOrError.isFailure) {
return Result.fail(new Error("Error creating user context"));
}
await this._tabContactRepo.registerContext(contextOrError.data, transaction);
// 🔹 Generar Access Token y Refresh Token
user.accessToken = this._authProvider.generateAccessToken({
const accessToken = this._authProvider.generateAccessToken({
userId: user.id.toString(),
email: email.toString(),
tabId: tabId.toString(),
roles: ["USER"],
});
user.refreshToken = this._authProvider.generateRefreshToken({
const refreshToken = this._authProvider.generateRefreshToken({
userId: user.id.toString(),
});
return Result.ok(user);
return Result.ok({
user,
tokens: {
accessToken,
refreshToken,
},
});
});
} catch (error: unknown) {
return Result.fail(error as Error);
}
}
/**
* 🔹 `selectCompany`
* Permite a un usuario seleccionar una empresa activa en la sesión bajo transacción.
*/
/*static async selectCompany(
userId: string,
companyId: string
): Promise<Result<{ message: string }, Error>> {
return await authUserRepository.executeTransaction(async (transaction) => {
const user = await authUserRepository.findById(userId, transaction);
if (user.isFailure) {
return Result.fail(new Error("User not found"));
}
const isAssociated = await authUserRepository.isUserAssociatedWithCompany(
userId,
companyId,
transaction
);
if (!isAssociated) {
return Result.fail(new Error("User does not have access to this company"));
}
return Result.ok({ message: "Company selected successfully" });
});
}*/
/**
* 🔹 `logout`
* Simula el cierre de sesión de un usuario. No requiere transacción.
*/
/*static logout(): Result<{ message: string }, never> {
return Result.ok({ message: "Logged out successfully" });
}*/
}

View File

@ -1,10 +1,12 @@
import { createSequelizeTransactionManager } from "@common/infrastructure";
import { createAuthenticatedUserRepository } from "../infraestructure";
import { createAuthenticatedUserRepository, createTabContextRepository } from "../infraestructure";
import { createPassportAuthProvider } from "../infraestructure/passport/passport-auth-provider";
import { IAuthProvider } from "./auth-provider.interface";
import { IAuthService } from "./auth-service.interface";
import { AuthService } from "./auth.service";
import { ITabContextService } from "./tab-context-service.interface";
import { TabContextService } from "./tab-context.service";
export * from "./auth-provider.interface";
export * from "./auth-service.interface";
@ -12,10 +14,23 @@ export * from "./auth-service.interface";
export const createAuthService = (): IAuthService => {
const transactionManager = createSequelizeTransactionManager();
const authenticatedUserRepository = createAuthenticatedUserRepository();
const tabContextRepository = createTabContextRepository();
const authProvider: IAuthProvider = createPassportAuthProvider(
authenticatedUserRepository,
transactionManager
);
return new AuthService(authenticatedUserRepository, transactionManager, authProvider);
return new AuthService(
authenticatedUserRepository,
tabContextRepository,
transactionManager,
authProvider
);
};
export const createTabContextService = (): ITabContextService => {
const transactionManager = createSequelizeTransactionManager();
const tabContextRepository = createTabContextRepository();
return new TabContextService(tabContextRepository, transactionManager);
};

View File

@ -0,0 +1,14 @@
import { Result, UniqueID } from "@common/domain";
import { TabContext } from "../domain";
export interface ITabContextService {
getContextByTabId(tabId: UniqueID): Promise<Result<TabContext, Error>>;
createContext(params: {
tabId: UniqueID;
userId: UniqueID;
companyId: UniqueID;
branchId: UniqueID;
}): Promise<Result<TabContext, Error>>;
assignCompany(tabId: UniqueID, companyId: UniqueID): Promise<Result<void, Error>>;
removeContext(tabId: UniqueID): Promise<Result<void, Error>>;
}

View File

@ -0,0 +1,117 @@
import { Result, UniqueID } from "@common/domain";
import { ITransactionManager } from "@common/infrastructure/database";
import { TabContext } from "../domain";
import { ITabContextRepository } from "../domain/repositories/tab-context-repository.interface";
import { ITabContextService } from "./tab-context-service.interface";
export class TabContextService implements ITabContextService {
private readonly _respository!: ITabContextRepository;
private readonly _transactionManager!: ITransactionManager;
constructor(repository: ITabContextRepository, transactionManager: ITransactionManager) {
this._respository = repository;
this._transactionManager = transactionManager;
}
/**
* Obtiene el contexto de una pestaña por su ID
*/
async getContextByTabId(tabId: UniqueID): Promise<Result<TabContext, Error>> {
try {
return await this._transactionManager.complete(async (transaction) => {
// Verificar si la pestaña existe
const tabContextOrError = await this._respository.getContextByTabId(tabId, transaction);
if (tabContextOrError.isSuccess && !tabContextOrError.data) {
return Result.fail(new Error("Invalid or expired Tab ID"));
}
if (tabContextOrError.isFailure) {
return Result.fail(tabContextOrError.error);
}
return Result.ok(tabContextOrError.data);
});
} catch (error: unknown) {
return Result.fail(error as Error);
}
}
/**
* Registra un nuevo contexto de pestaña para un usuario
*/
async createContext(params: {
tabId: UniqueID;
userId: UniqueID;
companyId: UniqueID;
branchId: UniqueID;
}): Promise<Result<TabContext, Error>> {
const { tabId, userId, companyId, branchId } = params;
if (!userId || !tabId) {
return Result.fail(new Error("User ID and Tab ID are required"));
}
try {
return await this._transactionManager.complete(async (transaction) => {
const contextOrError = TabContext.create(
{
userId,
tabId,
companyId,
branchId,
},
UniqueID.generateNewID().data
);
if (contextOrError.isFailure) {
return Result.fail(contextOrError.error);
}
await this._respository.registerContext(contextOrError.data, transaction);
return Result.ok(contextOrError.data);
});
} catch (error: unknown) {
return Result.fail(error as Error);
}
}
/**
* Asigna una empresa activa a un contexto de pestaña
*/
async assignCompany(tabId: UniqueID, companyId: UniqueID): Promise<Result<void, Error>> {
if (!companyId || !tabId) {
return Result.fail(new Error("Tab ID and Company ID are required"));
}
try {
return await this._transactionManager.complete(async (transaction) => {
// Verificar si la pestaña existe
const tabContextOrError = await this._respository.contextExists(tabId, transaction);
if (tabContextOrError.isFailure || !tabContextOrError.data) {
return Result.fail(new Error("Invalid or expired Tab ID"));
}
return await this._respository.updateCompanyByTabId(tabId, companyId, transaction);
});
} catch (error: unknown) {
return Result.fail(error as Error);
}
}
/**
* Elimina un contexto de pestaña por su ID
*/
async removeContext(tabId: UniqueID): Promise<Result<void, Error>> {
if (!tabId) {
return Result.fail(new Error("Tab ID is required"));
}
try {
return await this._transactionManager.complete(async (transaction) => {
return await this._respository.deleteContextByTabId(tabId, transaction);
});
} catch (error: unknown) {
return Result.fail(error as Error);
}
}
}

View File

@ -1,11 +1,11 @@
import { AggregateRoot, Result, UniqueID } from "@common/domain";
import { UserAuthenticatedEvent } from "../events";
import { EmailAddress, PasswordHash, Username } from "../value-objects";
import { EmailAddress, HashPassword, PlainPassword, Username } from "../value-objects";
export interface IAuthenticatedUserProps {
username: Username;
email: EmailAddress;
passwordHash: PasswordHash;
hashPassword: HashPassword;
roles: string[];
}
@ -19,7 +19,9 @@ export interface IAuthenticatedUser {
isUser: boolean;
isAdmin: boolean;
comparePassword(password: PasswordHash | string): Promise<boolean>;
contexts: ICollection<QuoteItem>;
verifyPassword(candidatePassword: PlainPassword): Promise<boolean>;
getRoles(): string[];
toPersistenceData(): any;
}
@ -45,12 +47,8 @@ export class AuthenticatedUser
return (this._props.roles || []).some((r) => r === role);
}
comparePassword(password: PasswordHash | string): Promise<boolean> {
if (typeof password === "string") {
return this._props.passwordHash.compare(password);
} else {
return this._props.passwordHash.compare(password.toString());
}
verifyPassword(candidatePassword: PlainPassword): Promise<boolean> {
return this._props.hashPassword.verifyPassword(candidatePassword.toString());
}
getRoles(): string[] {
@ -81,10 +79,10 @@ export class AuthenticatedUser
id: this._id.toString(),
username: this._props.username.toString(),
email: this._props.email.toString(),
password: this._props.passwordHash.toString(),
hash_password: this._props.hashPassword.toString(),
roles: this._props.roles.map((role) => role.toString()),
accessToken: this.accessToken,
refreshToken: this.refreshToken,
access_token: this.accessToken,
refresh_token: this.refreshToken,
};
}
}

View File

@ -0,0 +1 @@
export * from "./tab-context";

View File

@ -0,0 +1,63 @@
import { DomainEntity, Result, UniqueID } from "@common/domain";
export interface ITabContextProps {
tabId: UniqueID;
userId: UniqueID;
companyId: UniqueID;
branchId: UniqueID;
}
export interface ITabContext {
tabId: UniqueID;
userId: UniqueID;
companyId: UniqueID;
branchId: UniqueID;
hasCompanyAssigned(): boolean;
hasBranchAssigned(): boolean;
toPersistenceData(): any;
}
export class TabContext extends DomainEntity<ITabContextProps> implements ITabContext {
static create(props: ITabContextProps, id?: UniqueID): Result<TabContext, Error> {
return Result.ok(new this(props, id));
}
get tabId(): UniqueID {
return this._props.tabId;
}
get userId(): UniqueID {
return this._props.userId;
}
get companyId(): UniqueID {
return this._props.companyId;
}
get branchId(): UniqueID {
return this._props.branchId;
}
hasCompanyAssigned(): boolean {
return this._props.companyId.isDefined();
}
hasBranchAssigned(): boolean {
return this._props.branchId.isDefined();
}
/**
* 🔹 Devuelve una representación lista para persistencia
*/
toPersistenceData(): any {
return {
id: this._id.toString(),
tab_id: this.tabId.toString(),
user_id: this.userId.toString(),
company_id: this.companyId.toString(),
branch_id: this.branchId.toString(),
};
}
}

View File

@ -1,4 +1,5 @@
export * from "./aggregates/authenticated-user";
export * from "./events/user-authenticated.event";
export * from "./aggregates";
export * from "./entities";
export * from "./events";
export * from "./repositories";
export * from "./value-objects";

View File

@ -3,10 +3,7 @@ import { AuthenticatedUser } from "../aggregates";
import { EmailAddress } from "../value-objects";
export interface IAuthenticatedUserRepository {
findUserByEmail(
email: EmailAddress,
transaction?: any
): Promise<Result<AuthenticatedUser | null, Error>>;
getUserByEmail(email: EmailAddress, transaction?: any): Promise<Result<AuthenticatedUser, Error>>;
userExists(email: EmailAddress, transaction?: any): Promise<Result<boolean, Error>>;
createUser(user: AuthenticatedUser, transaction?: any): Promise<Result<void, Error>>;
}

View File

@ -0,0 +1,15 @@
import { Result, UniqueID } from "@common/domain";
import { Transaction } from "sequelize";
import { TabContext } from "../entities";
export interface ITabContextRepository {
getContextByTabId(tabId: UniqueID, transaction?: any): Promise<Result<TabContext, Error>>;
registerContext(context: TabContext, transaction?: Transaction): Promise<Result<void, Error>>;
contextExists(tabId: UniqueID, transaction?: any): Promise<Result<boolean, Error>>;
updateCompanyByTabId(
tabId: UniqueID,
companyId: UniqueID,
transaction?: Transaction
): Promise<Result<void, Error>>;
deleteContextByTabId(tabId: UniqueID, transaction?: any): Promise<Result<void, Error>>;
}

View File

@ -1,33 +1,33 @@
import { PasswordHash } from "./password-hash";
import { HashPassword } from "./hash-password";
describe("PasswordHash Value Object", () => {
it("should hash a valid password", async () => {
const result = await PasswordHash.create("StrongPass123");
const result = HashPassword.create("StrongPass123");
expect(result.isSuccess).toBe(true);
expect(result.data.getValue()).not.toBe("StrongPass123"); // Should be hashed
});
it("should return an error for short password", async () => {
const result = await PasswordHash.create("12345");
const result = HashPassword.create("12345");
expect(result.isSuccess).toBe(true);
expect(result.error.message).toBe("Password must be at least 6 characters long");
});
it("should validate password comparison correctly", async () => {
const result = await PasswordHash.create("SecurePass123");
const result = HashPassword.create("SecurePass123");
expect(result.isSuccess).toBe(true);
const isValid = await result.data.compare("SecurePass123");
const isValid = await result.data.verifyPassword("SecurePass123");
expect(isValid).toBe(true);
});
it("should fail password comparison for incorrect passwords", async () => {
const result = await PasswordHash.create("SecurePass123");
const result = HashPassword.create("SecurePass123");
expect(result.isSuccess).toBe(true);
const isValid = await result.data.compare("WrongPassword");
const isValid = await result.data.verifyPassword("WrongPassword");
expect(isValid).toBe(false);
});
});

View File

@ -0,0 +1,35 @@
import { Result, ValueObject } from "@common/domain";
import bcrypt from "bcrypt";
import { z } from "zod";
export class HashPassword extends ValueObject<string> {
private static readonly SALT_ROUNDS = 10;
static create(plainPassword: string): Result<HashPassword, Error> {
const result = HashPassword.validate(plainPassword);
if (!result.success) {
return Result.fail(new Error(result.error.errors[0].message));
}
const hashed = bcrypt.hashSync(result.data, this.SALT_ROUNDS);
return Result.ok(new HashPassword(hashed));
}
private static validate(password: string) {
const schema = z.string().min(6, { message: "Password must be at least 6 characters long" });
return schema.safeParse(password);
}
static createFromHash(hashedPassword: string): Result<HashPassword, Error> {
return Result.ok(new HashPassword(hashedPassword));
}
static createFromPlainText(plainTextPassword: string): Result<HashPassword, Error> {
return HashPassword.create(plainTextPassword);
}
async verifyPassword(plainTextPassword: string): Promise<boolean> {
return await bcrypt.compare(plainTextPassword, this._value);
}
}

View File

@ -1,4 +1,5 @@
export * from "./auth-user-roles";
export * from "./email-address";
export * from "./password-hash";
export * from "./hash-password";
export * from "./plain-password";
export * from "./username";

View File

@ -1,31 +0,0 @@
import { Result, ValueObject } from "@common/domain";
import bcrypt from "bcrypt";
import { z } from "zod";
export class PasswordHash extends ValueObject<string> {
private static readonly SALT_ROUNDS = 10;
static create(plainPassword: string): Result<PasswordHash, Error> {
const result = PasswordHash.validate(plainPassword);
if (!result.success) {
return Result.fail(new Error(result.error.errors[0].message));
}
const hashed = bcrypt.hashSync(result.data, this.SALT_ROUNDS);
return Result.ok(new PasswordHash(hashed));
}
private static validate(password: string) {
const schema = z.string().min(6, { message: "Password must be at least 6 characters long" });
return schema.safeParse(password);
}
static fromHash(hash: string): PasswordHash {
return new PasswordHash(hash);
}
async compare(plainPassword: string): Promise<boolean> {
return await bcrypt.compare(plainPassword, this._value);
}
}

View File

@ -0,0 +1,19 @@
import { Result, ValueObject } from "@common/domain";
import { z } from "zod";
export class PlainPassword extends ValueObject<string> {
static create(plainTextPassword: string): Result<PlainPassword, Error> {
const result = PlainPassword.validate(plainTextPassword);
if (!result.success) {
return Result.fail(new Error(result.error.errors[0].message));
}
return Result.ok(new PlainPassword(result.data));
}
private static validate(password: string) {
const schema = z.string().min(6, { message: "Password must be at least 6 characters long" });
return schema.safeParse(password);
}
}

View File

@ -1,14 +1,8 @@
import { Result } from "@common/domain";
import { AuthenticatedUser } from "@contexts/auth/domain";
import { AuthUserModel } from "../sequelize";
export interface IAuthenticatedUserMapper {
/**
* 🔹 Convierte una entidad de la base de datos en un agregado de dominio `AuthenticatedUser`
*/
toDomain(entity: any): Result<AuthenticatedUser, Error>;
/**
* 🔹 Convierte un agregado `AuthenticatedUser` en un objeto listo para persistencia
*/
toPersistence(aggregate: AuthenticatedUser): any;
toDomain(entity: AuthUserModel): Result<AuthenticatedUser, Error>;
toPersistence(aggregate: AuthenticatedUser): AuthUserModel;
}

View File

@ -1,12 +1,13 @@
import { Result, UniqueID } from "@common/domain";
import { AuthenticatedUser, EmailAddress, PasswordHash, Username } from "@contexts/auth/domain";
import { AuthenticatedUser, EmailAddress, HashPassword, Username } from "@contexts/auth/domain";
import { AuthUserModel } from "../sequelize";
import { IAuthenticatedUserMapper } from "./authenticated-user-mapper.interface";
export class AuthenticatedUserMapper implements IAuthenticatedUserMapper {
/**
* 🔹 Convierte una entidad de la base de datos en un agregado de dominio `AuthenticatedUser`
*/
toDomain(entity: any): Result<AuthenticatedUser, Error> {
toDomain(entity: AuthUserModel): Result<AuthenticatedUser, Error> {
if (!entity) {
return Result.fail(new Error("Entity not found"));
}
@ -14,9 +15,9 @@ export class AuthenticatedUserMapper implements IAuthenticatedUserMapper {
// Crear Value Objects asegurando que sean válidos
const uniqueIdResult = UniqueID.create(entity.id);
const usernameResult = Username.create(entity.username);
const passwordHashResult = PasswordHash.create(entity.passwordHash);
const passwordHashResult = HashPassword.createFromHash(entity.hash_password);
const emailResult = EmailAddress.create(entity.email);
1;
// Validar que no haya errores en la creación de los Value Objects
const okOrError = Result.combine([
uniqueIdResult,
@ -33,9 +34,8 @@ export class AuthenticatedUserMapper implements IAuthenticatedUserMapper {
{
username: usernameResult.data!,
email: emailResult.data!,
passwordHash: passwordHashResult.data!,
hashPassword: passwordHashResult.data!,
roles: entity.roles || [],
token: entity.token,
},
uniqueIdResult.data!
);
@ -44,7 +44,7 @@ export class AuthenticatedUserMapper implements IAuthenticatedUserMapper {
/**
* 🔹 Convierte un agregado `AuthenticatedUser` en un objeto listo para persistencia
*/
toPersistence(authenticatedUser: AuthenticatedUser): any {
toPersistence(authenticatedUser: AuthenticatedUser): AuthUserModel {
return authenticatedUser.toPersistenceData();
}
}

View File

@ -1,2 +1,4 @@
export * from "./authenticated-user-mapper.interface";
export * from "./authenticated-user.mapper";
export * from "./tab-context-mapper.interface";
export * from "./tab-context.mapper";

View File

@ -0,0 +1,8 @@
import { Result } from "@common/domain";
import { TabContext } from "@contexts/auth/domain";
import { TabContextModel } from "../sequelize";
export interface ITabContextMapper {
toDomain(entity: TabContextModel): Result<TabContext, Error>;
toPersistence(aggregate: TabContext): TabContextModel;
}

View File

@ -0,0 +1,48 @@
import { Result, UniqueID } from "@common/domain";
import { TabContext } from "@contexts/auth/domain";
import { TabContextModel } from "../sequelize";
import { ITabContextMapper } from "./tab-context-mapper.interface";
export class TabContextMapper implements ITabContextMapper {
toDomain(entity: TabContextModel): Result<TabContext, Error> {
if (!entity) {
return Result.fail(new Error("Entity not found"));
}
// Crear Value Objects asegurando que sean válidos
const uniqueIdResult = UniqueID.create(entity.id);
const tabIdResult = UniqueID.create(entity.tab_id);
const userIdResult = UniqueID.create(entity.user_id);
const companyIdResult = UniqueID.create(entity.company_id, false);
const brachIdResult = UniqueID.create(entity.branch_id, false);
// Validar que no haya errores en la creación de los Value Objects
const okOrError = Result.combine([
uniqueIdResult,
tabIdResult,
userIdResult,
companyIdResult,
brachIdResult,
]);
if (okOrError.isFailure) {
return Result.fail(okOrError.error.message);
}
// Crear el agregado de dominio
return TabContext.create(
{
tabId: tabIdResult.data!,
userId: userIdResult.data!,
companyId: companyIdResult.data,
branchId: brachIdResult.data,
},
uniqueIdResult.data!
);
}
toPersistence(tabContext: TabContext): TabContextModel {
return tabContext.toPersistenceData();
}
}
export const createTabContextMapper = (): ITabContextMapper => new TabContextMapper();

View File

@ -5,6 +5,7 @@ import {
AuthenticatedUser,
EmailAddress,
IAuthenticatedUserRepository,
PlainPassword,
} from "@contexts/auth/domain";
import jwt from "jsonwebtoken";
import passport from "passport";
@ -20,6 +21,22 @@ export class PassportAuthProvider implements IAuthProvider {
private readonly _repository: IAuthenticatedUserRepository;
private readonly _transactionManager!: ITransactionManager;
private async _verifyUser(email: string, password: string): Promise<AuthenticatedUser | null> {
const emailVO = EmailAddress.create(email);
if (emailVO.isFailure) return Promise.resolve(null);
const passwordVO = PlainPassword.create(password);
if (passwordVO.isFailure) return Promise.resolve(null);
const userResult = await this._repository.getUserByEmail(emailVO.data);
if (userResult.isFailure || !userResult.data) return Promise.resolve(null);
const user = userResult.data;
const isValidPassword = await user.verifyPassword(passwordVO.data);
return !isValidPassword ? Promise.resolve(null) : Promise.resolve(user);
}
/**
* 🔹 Configura PassportJS
*/
@ -31,10 +48,9 @@ export class PassportAuthProvider implements IAuthProvider {
passport.use(
"jwt",
new JwtStrategy(jwtOptions, (payload, done) => {
new JwtStrategy(jwtOptions, (tokenPayload, done) => {
try {
console.log(payload);
return done(null, payload);
return done(null, tokenPayload);
} catch (error) {
return done(error, false);
}
@ -47,7 +63,7 @@ export class PassportAuthProvider implements IAuthProvider {
{ usernameField: "email", passwordField: "password" },
async (email, password, done) => {
try {
const user = await this.verifyUser(email, password);
const user = await this._verifyUser(email, password);
return user
? done(null, user)
: done(null, false, { message: "Invalid email or password" });
@ -67,30 +83,17 @@ export class PassportAuthProvider implements IAuthProvider {
this.initializePassport();
}
generateAccessToken(payload: any): string {
return jwt.sign(payload, SECRET_KEY, { expiresIn: ACCESS_EXPIRATION });
generateAccessToken(payload: object): string {
return jwt.sign(payload, SECRET_KEY, { expiresIn: String(ACCESS_EXPIRATION) });
}
generateRefreshToken(payload: any): string {
generateRefreshToken(payload: object): string {
return jwt.sign(payload, SECRET_KEY, { expiresIn: REFRESH_EXPIRATION });
}
verifyToken(token: string): any {
return jwt.verify(token, SECRET_KEY);
}
async verifyUser(email: string, password: string): Promise<AuthenticatedUser | null> {
const emailVO = EmailAddress.create(email);
if (emailVO.isFailure) return Promise.resolve(null);
const userResult = await this._repository.findUserByEmail(emailVO.data);
if (userResult.isFailure || !userResult.data) return Promise.resolve(null);
const user = userResult.data;
const isValidPassword = await user.comparePassword(password);
return !isValidPassword ? Promise.resolve(null) : Promise.resolve(user);
}
}
export const createPassportAuthProvider = (
@ -101,3 +104,5 @@ export const createPassportAuthProvider = (
const _repository = repository || createAuthenticatedUserRepository();
return new PassportAuthProvider(_repository, _transactionManager);
};
export const initializePassportAuthProvide = () => createPassportAuthProvider();

View File

@ -1,6 +1,19 @@
import { DataTypes, InferAttributes, InferCreationAttributes, Model, Sequelize } from "sequelize";
import {
DataTypes,
InferAttributes,
InferCreationAttributes,
Model,
NonAttribute,
Sequelize,
} from "sequelize";
import { TabContextCreationAttributes, TabContextModel } from "./tab-context.model";
export type AuthUserCreationAttributes = InferCreationAttributes<AuthUserModel>;
export type AuthUserCreationAttributes = InferCreationAttributes<
AuthUserModel,
{ omit: "contexts" }
> & {
contexts: TabContextCreationAttributes[];
};
export class AuthUserModel extends Model<
InferAttributes<AuthUserModel>,
@ -11,13 +24,22 @@ export class AuthUserModel extends Model<
return Promise.resolve();
}*/
static associate(connection: Sequelize) {}
static associate(connection: Sequelize) {
const { TabContextModel } = connection.models;
AuthUserModel.hasMany(TabContextModel, {
as: "contexts",
foreignKey: "user_id",
onDelete: "CASCADE",
});
}
declare id: string;
declare username: string;
declare email: string;
declare password: string;
declare hash_password: string;
declare roles: string[];
declare isActive: boolean;
declare contexts: NonAttribute<TabContextModel[]>;
}
export default (sequelize: Sequelize) => {
@ -35,7 +57,7 @@ export default (sequelize: Sequelize) => {
type: DataTypes.STRING,
allowNull: false,
},
password: {
hash_password: {
type: DataTypes.STRING,
allowNull: false,
},
@ -52,10 +74,6 @@ export default (sequelize: Sequelize) => {
this.setDataValue("roles", rawValue);
},
},
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: true,
},
},
{
sequelize,
@ -68,6 +86,12 @@ export default (sequelize: Sequelize) => {
deletedAt: "deleted_at",
indexes: [{ name: "email_idx", fields: ["email"], unique: true }],
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
defaultScope: {},
scopes: {},
}
);
return AuthUserModel;

View File

@ -49,20 +49,21 @@ export class AuthenticatedUserRepository
}
}
async findUserByEmail(
async getUserByEmail(
email: EmailAddress,
transaction?: Transaction
): Promise<Result<AuthenticatedUser | null, Error>> {
): Promise<Result<AuthenticatedUser, Error>> {
try {
const rawUser: any = await this._findById(
const rawUser: any = await this._getBy(
AuthUserModel,
"email",
email.toString(),
{},
transaction
);
if (!rawUser === true) {
return Result.ok(null);
return Result.fail(new Error("User with email not exists"));
}
return this._mapper.toDomain(rawUser);

View File

@ -1,2 +1,4 @@
export * from "./auth-user.model";
export * from "./authenticated-user.repository";
export * from "./tab-context.model";
export * from "./tab-context.repository";

View File

@ -0,0 +1,87 @@
import {
DataTypes,
InferAttributes,
InferCreationAttributes,
Model,
NonAttribute,
Sequelize,
} from "sequelize";
import { AuthUserModel } from "./auth-user.model";
export type TabContextCreationAttributes = InferCreationAttributes<
TabContextModel,
{ omit: "user" }
>;
export class TabContextModel extends Model<
InferAttributes<TabContextModel, { omit: "user" }>,
InferCreationAttributes<TabContextModel, { omit: "user" }>
> {
// To avoid table creation
/*static async sync(): Promise<any> {
return Promise.resolve();
}*/
static associate(connection: Sequelize) {
const { AuthUserModel } = connection.models;
TabContextModel.belongsTo(AuthUserModel, {
as: "user",
foreignKey: "user_id",
onDelete: "CASCADE",
});
}
declare id: string;
declare tab_id: string;
declare user_id: string;
declare company_id: string;
declare branch_id: string;
declare user: NonAttribute<AuthUserModel>;
}
export default (sequelize: Sequelize) => {
TabContextModel.init(
{
id: {
type: DataTypes.UUID,
primaryKey: true,
},
user_id: {
type: DataTypes.UUID,
allowNull: false,
},
tab_id: {
type: DataTypes.UUID,
allowNull: false,
},
company_id: {
type: DataTypes.UUID,
allowNull: false,
},
branch_id: {
type: DataTypes.UUID,
allowNull: false,
},
},
{
sequelize,
tableName: "user_tab_contexts",
paranoid: true, // softs deletes
timestamps: true,
createdAt: "created_at",
updatedAt: "updated_at",
deletedAt: "deleted_at",
indexes: [{ name: "tab_id_idx", fields: ["tab_id"], unique: true }],
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
defaultScope: {},
scopes: {},
}
);
return TabContextModel;
};

View File

@ -0,0 +1,132 @@
import { Result, UniqueID } from "@common/domain";
import { SequelizeRepository } from "@common/infrastructure";
import { TabContext } from "@contexts/auth/domain/";
import { ITabContextRepository } from "@contexts/auth/domain/repositories/tab-context-repository.interface";
import { Transaction } from "sequelize";
import { createTabContextMapper, ITabContextMapper } from "../mappers";
import { TabContextModel } from "./tab-context.model";
export class TabContextRepository
extends SequelizeRepository<TabContext>
implements ITabContextRepository
{
private readonly _mapper!: ITabContextMapper;
/**
* 🔹 Función personalizada para mapear errores de unicidad en autenticación
*/
private _customErrorMapper(error: Error): string | null {
if (error.name === "SequelizeUniqueConstraintError") {
return "Tab context already exists";
}
return null;
}
constructor(mapper: ITabContextMapper) {
super();
this._mapper = mapper;
}
async getContextByTabId(
tabId: UniqueID,
transaction?: Transaction
): Promise<Result<TabContext, Error>> {
try {
const rawContext = await this._getBy(
TabContextModel,
"tab_id",
tabId.toString(),
{},
transaction
);
if (!rawContext === true) {
return Result.fail(new Error("Tab context not exists"));
}
return this._mapper.toDomain(rawContext);
} catch (error: any) {
return this._handleDatabaseError(error, this._customErrorMapper);
}
}
async contextExists(tabId: UniqueID, transaction?: any): Promise<Result<boolean, Error>> {
try {
const result: any = await this._exists(
TabContextModel,
"tab_id",
tabId.toString(),
transaction
);
return Result.ok(Boolean(result));
} catch (error: any) {
return this._handleDatabaseError(error, this._customErrorMapper);
}
}
/**
* Crea un contexto para un tab id o actualiza si ya existe
* @param context
* @param transaction
* @returns
*/
async registerContext(
context: TabContext,
transaction?: Transaction
): Promise<Result<void, Error>> {
try {
const { id } = context;
const persistenceData = this._mapper.toPersistence(context);
await this._save(TabContextModel, id, persistenceData, {}, transaction);
return Result.ok();
} catch (error: any) {
return this._handleDatabaseError(error, this._customErrorMapper);
}
}
async updateCompanyByTabId(
tabId: UniqueID,
companyId: UniqueID,
transaction?: Transaction
): Promise<Result<void, Error>> {
try {
await TabContextModel.update(
{ company_id: companyId.toString() },
{
where: {
tab_id: tabId.toString(),
},
transaction,
}
);
return Result.ok();
} catch (error: any) {
return this._handleDatabaseError(error, this._customErrorMapper);
}
}
async deleteContextByTabId(tabId: UniqueID, transaction?: any): Promise<Result<void, Error>> {
try {
await TabContextModel.destroy({
where: {
tab_id: tabId.toString(),
},
transaction,
force: false,
});
return Result.ok();
} catch (error: any) {
return this._handleDatabaseError(error, this._customErrorMapper);
}
}
}
export const createTabContextRepository = (): ITabContextRepository => {
const tabContextMapper = createTabContextMapper();
return new TabContextRepository(tabContextMapper);
};

View File

@ -1,6 +1,7 @@
import { Result, UniqueID } from "@common/domain";
import { ExpressController } from "@common/presentation";
import { createAuthService, IAuthService } from "@contexts/auth/application";
import { EmailAddress, PasswordHash } from "@contexts/auth/domain";
import { EmailAddress, PlainPassword } from "@contexts/auth/domain";
import { ILoginPresenter, LoginPresenter } from "./login.presenter";
class LoginController extends ExpressController {
@ -14,16 +15,21 @@ class LoginController extends ExpressController {
}
async executeImpl() {
const tabId = this.req.headers["x-tab-id"];
const emailVO = EmailAddress.create(this.req.body.email);
const passwordHashVO = PasswordHash.create(this.req.body.password);
const plainPasswordVO = PlainPassword.create(this.req.body.password);
const tabIdVO = UniqueID.create(String(tabId));
if ([emailVO, passwordHashVO].some((r) => r.isFailure)) {
return this.clientError("Invalid input data");
const resultValidation = Result.combine([emailVO, plainPasswordVO, tabIdVO]);
if (resultValidation.isFailure) {
return this.clientError("Invalid input data", resultValidation.error);
}
const userOrError = await this._authService.loginUser({
email: emailVO.data,
passwordHash: passwordHashVO.data,
plainPassword: plainPasswordVO.data,
tabId: tabIdVO.data,
});
if (userOrError.isFailure) {

View File

@ -2,20 +2,40 @@ import { AuthenticatedUser } from "@contexts/auth/domain";
import { ILoginUserResponseDTO } from "../../dto";
export interface ILoginPresenter {
map: (user: AuthenticatedUser) => ILoginUserResponseDTO;
map: (data: {
user: AuthenticatedUser;
tokens: {
accessToken: string;
refreshToken: string;
};
}) => ILoginUserResponseDTO;
}
export const LoginPresenter: ILoginPresenter = {
map: (user: AuthenticatedUser): ILoginUserResponseDTO => {
//const { user, token, refreshToken } = loginUser;
//const roles = user.getRoles()?.map((rol) => rol.toString()) || [];
map: (data: {
user: AuthenticatedUser;
tokens: {
accessToken: string;
refreshToken: string;
};
}): ILoginUserResponseDTO => {
const {
user,
tokens: { accessToken, refreshToken },
} = data;
const userData = user.toPersistenceData();
return {
user_id: userData,
access_token: userData.accessToken,
refresh_token: userData.refreshToken,
user: {
id: userData.id,
email: userData.email,
username: userData.username,
},
tokens: {
access_token: accessToken,
refresh_token: refreshToken,
},
};
},
};

View File

@ -0,0 +1 @@
export * from "./logout.controller";

View File

@ -0,0 +1,38 @@
import { UniqueID } from "@common/domain";
import { ExpressController } from "@common/presentation";
import { createAuthService, IAuthService } from "@contexts/auth/application";
class LogoutController extends ExpressController {
private readonly _authService!: IAuthService;
public constructor(authService: IAuthService) {
super();
this._authService = authService;
}
async executeImpl() {
const tabId = this.req.headers["x-tab-id"];
const tabIdVO = UniqueID.create(String(tabId));
if (tabIdVO.isFailure) {
return this.clientError("Invalid tab id", [tabIdVO.error]);
}
const userOrError = await this._authService.logoutUser({
email: emailVO.data,
plainPassword: plainPasswordVO.data,
tabId: tabIdVO.data,
});
if (userOrError.isFailure) {
return this.unauthorizedError(userOrError.error.message);
}
return this.ok();
}
}
export const createLogoutController = () => {
const authService = createAuthService();
return new LogoutController(authService);
};

View File

@ -1,6 +1,6 @@
import { ExpressController } from "@common/presentation";
import { createAuthService, IAuthService } from "@contexts/auth/application";
import { EmailAddress, PasswordHash, Username } from "@contexts/auth/domain";
import { EmailAddress, HashPassword, Username } from "@contexts/auth/domain";
import { IRegisterPresenter, RegisterPresenter } from "./register.presenter";
class RegisterController extends ExpressController {
@ -16,16 +16,16 @@ class RegisterController extends ExpressController {
async executeImpl() {
const emailVO = EmailAddress.create(this.req.body.email);
const usernameVO = Username.create(this.req.body.username);
const passwordHashVO = PasswordHash.create(this.req.body.password);
const hashPasswordVO = HashPassword.create(this.req.body.password);
if ([emailVO, usernameVO, passwordHashVO].some((r) => r.isFailure)) {
if ([emailVO, usernameVO, hashPasswordVO].some((r) => r.isFailure)) {
return this.clientError("Invalid input data");
}
const userOrError = await this._authService.registerUser({
username: usernameVO.data,
email: emailVO.data,
passwordHash: passwordHashVO.data,
hashPassword: hashPasswordVO.data,
});
if (userOrError.isFailure) {

View File

@ -5,9 +5,16 @@ export interface IRegisterUserResponseDTO {
}
export interface ILoginUserResponseDTO {
access_token: string;
refresh_token: string;
user_id: string;
user: {
id: string;
username: string;
email: string;
};
tokens: {
access_token: string;
refresh_token: string;
};
//tab_id: string;
}
export interface ILogoutResponseDTO {

View File

@ -1 +1,2 @@
export * from "./passport-auth.middleware";
export * from "./tab-context.middleware";

View File

@ -10,10 +10,10 @@ interface AuthenticatedRequest extends Request {
}
// Middleware para autenticar usando passport con el local-jwt strategy
const authenticateJwt = passport.authenticate("jwt", { session: false });
const _authenticateJwt = passport.authenticate("jwt", { session: false });
// Comprueba el rol del usuario
const authorizeUser = (condition: (user: AuthenticatedUser) => boolean) => {
const _authorizeUser = (condition: (user: AuthenticatedUser) => boolean) => {
return (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
const user = req.user as AuthenticatedUser;
if (!user || !condition(user)) {
@ -33,14 +33,14 @@ const authorizeUser = (condition: (user: AuthenticatedUser) => boolean) => {
};
// Verifica que el usuario esté autenticado
export const checkUser = [authenticateJwt, authorizeUser((user) => user.isUser)];
export const validateUser = [_authenticateJwt, _authorizeUser((user) => user.isUser)];
// Verifica que el usuario sea administrador
export const checkIsAdmin = [authenticateJwt, authorizeUser((user) => user.isAdmin)];
export const validateUserIsAdmin = [_authenticateJwt, _authorizeUser((user) => user.isAdmin)];
// Middleware para verificar que el usuario sea administrador o el dueño de los datos (self)
export const checkAdminOrSelf = [
authenticateJwt,
export const validateUserIsAdminOrOwner = [
_authenticateJwt,
(req: AuthenticatedRequest, res: Response, next: NextFunction) => {
const user = req.user as AuthenticatedUser;
const { userId } = req.params;

View File

@ -0,0 +1,60 @@
import { UniqueID } from "@common/domain";
import { ApiError, ExpressController } from "@common/presentation";
import { createTabContextService } from "@contexts/auth/application";
import { TabContext } from "@contexts/auth/domain";
import { NextFunction, Request, Response } from "express";
import httpStatus from "http-status";
// Extender el Request de Express para incluir el usuario autenticado optionalmente
interface TabContextRequest extends Request {
tabContext?: TabContext;
}
export const validateTabContextHeader = async (
req: TabContextRequest,
res: Response,
next: NextFunction
) => {
const tabId = String(req.headers["x-tab-id"]);
if (!tabId) {
return ExpressController.errorResponse(
new ApiError({
status: 401,
title: httpStatus["401"],
name: httpStatus["401_NAME"],
detail: "Tab ID is required",
}),
res
);
}
const tabIdOrError = UniqueID.create(tabId, false);
if (tabIdOrError.isFailure) {
return ExpressController.errorResponse(
new ApiError({
status: 422,
title: httpStatus["422"],
name: httpStatus["422_NAME"],
detail: "Invalid Tab ID",
}),
res
);
}
const contextOrError = await createTabContextService().getContextByTabId(tabIdOrError.data);
if (contextOrError.isFailure) {
return ExpressController.errorResponse(
new ApiError({
status: 401,
title: httpStatus["401"],
name: httpStatus["401_NAME"],
detail: "Invalid or expired Tab ID",
}),
res
);
}
const context = contextOrError.data;
req.tabContext = context;
next();
};

View File

@ -1,8 +1,7 @@
import { validateRequest } from "@common/presentation";
import {
createLoginController,
createRegisterController,
} from "@contexts/auth/presentation/controllers";
import { validateTabContextHeader, validateUser } from "@contexts/auth/presentation";
import { createLoginController } from "@contexts/auth/presentation/controllers";
import { createRegisterController } from "@contexts/auth/presentation/controllers/register/register.controller";
import { LoginUserSchema, RegisterUserSchema } from "@contexts/auth/presentation/dto";
import { Router } from "express";
@ -33,6 +32,7 @@ export const authRouter = (appRouter: Router) => {
* @apiGroup Authentication
* @apiVersion 1.0.0
*
* @apiHeader {String} Tab ID (x-tab-id)
* @apiBody {String} email User's email address.
* @apiBody {String} password User's password.
*
@ -41,25 +41,14 @@ export const authRouter = (appRouter: Router) => {
*
* @apiError (401) {String} message Invalid email or password.
*/
authRoutes.post("/login", validateRequest(LoginUserSchema), (req, res, next) => {
createLoginController().execute(req, res, next);
});
/**
* @api {post} /api/auth/select-company Select an active company
* @apiName SelectCompany
* @apiGroup Authentication
* @apiVersion 1.0.0
*
* @apiHeader {String} Authorization Bearer token.
*
* @apiBody {String} companyId The ID of the company to select.
*
* @apiSuccess (200) {String} message Success message.
*
* @apiError (403) {String} message Unauthorized or invalid company selection.
*/
//authRoutes.post("/select-company", authMiddleware, authController.selectCompany);
authRoutes.post(
"/login",
validateRequest(LoginUserSchema),
validateTabContextHeader,
(req, res, next) => {
createLoginController().execute(req, res, next);
}
);
/**
* @api {post} /api/auth/logout Logout user
@ -67,11 +56,15 @@ export const authRouter = (appRouter: Router) => {
* @apiGroup Authentication
* @apiVersion 1.0.0
*
* @apiHeader {String} Tab ID (x-tab-id)
* @apiHeader {String} Authorization Bearer token.
*
* @apiSuccess (200) {String} message Success message.
*/
//authRoutes.post("/logout", authMiddleware, authController.logout);
authRoutes.post("/logout", validateUser, validateTabContextHeader, (req, res, next) => {
res.sendStatus(200);
//createLogoutController().execute(req, res, next);
});
appRouter.use("/auth", authRoutes);
};