@ecopex/ecopex-framework 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/.env +73 -0
  2. package/README.md +248 -0
  3. package/bun.lockb +0 -0
  4. package/config/swagger/admin.js +44 -0
  5. package/config/swagger/api.js +19 -0
  6. package/database/migrations/20240000135243_timezones.js +22 -0
  7. package/database/migrations/20240000135244_countries.js +23 -0
  8. package/database/migrations/20240000135244_create_admins_table.js +66 -0
  9. package/database/migrations/20240000135244_currencies.js +21 -0
  10. package/database/migrations/20240000135244_languages.js +21 -0
  11. package/database/migrations/20240000135244_taxes.js +10 -0
  12. package/database/migrations/20240000135245_sites.js +37 -0
  13. package/database/migrations/20240000135246_payment_methods.js +33 -0
  14. package/database/migrations/20251016113547_devices.js +37 -0
  15. package/database/migrations/20251019192600_users.js +62 -0
  16. package/database/migrations/20251019213551_language_lines.js +35 -0
  17. package/database/migrations/20251222214131_category_groups.js +18 -0
  18. package/database/migrations/20251222214619_categories.js +27 -0
  19. package/database/migrations/20251222214848_brands.js +23 -0
  20. package/database/migrations/20251222214946_products.js +30 -0
  21. package/database/migrations/20251222215428_product_images.js +18 -0
  22. package/database/migrations/20251222215553_options.js +30 -0
  23. package/database/migrations/20251222215806_variants.js +23 -0
  24. package/database/migrations/20251222215940_attributes.js +25 -0
  25. package/database/migrations/20251222220135_discounts.js +15 -0
  26. package/database/migrations/20251222220253_reviews.js +22 -0
  27. package/database/migrations/20251222220341_favorites.js +10 -0
  28. package/database/migrations/20251222220422_search_logs.js +17 -0
  29. package/database/migrations/20251222220636_orders.js +16 -0
  30. package/database/migrations/20251222220806_order_items.js +19 -0
  31. package/database/migrations/20251222221317_order_statuses.js +10 -0
  32. package/database/migrations/20251222221446_order_payments.js +13 -0
  33. package/database/migrations/20251222221654_order_addresses.js +23 -0
  34. package/database/migrations/20251222221807_order_status_logs.js +13 -0
  35. package/database/seeds/admins.js +37 -0
  36. package/database/seeds/countries.js +203 -0
  37. package/database/seeds/currencies.js +165 -0
  38. package/database/seeds/languages.js +113 -0
  39. package/database/seeds/timezones.js +149 -0
  40. package/ecosystem.config.js +26 -0
  41. package/env.example +73 -0
  42. package/knexfile.js +3 -0
  43. package/libraries/2fa.js +22 -0
  44. package/libraries/aws.js +63 -0
  45. package/libraries/bcrypt.js +284 -0
  46. package/libraries/controls.js +113 -0
  47. package/libraries/date.js +14 -0
  48. package/libraries/general.js +8 -0
  49. package/libraries/image.js +57 -0
  50. package/libraries/jwt.js +178 -0
  51. package/libraries/knex.js +7 -0
  52. package/libraries/slug.js +14 -0
  53. package/libraries/stores.js +22 -0
  54. package/libraries/upload.js +194 -0
  55. package/locales/en/messages.json +4 -0
  56. package/locales/en/sql.json +3 -0
  57. package/locales/en/validation.json +52 -0
  58. package/locales/es/validation.json +52 -0
  59. package/locales/tr/validation.json +59 -0
  60. package/package.json +75 -0
  61. package/routes/admin/auto/admins.json +63 -0
  62. package/routes/admin/auto/devices.json +37 -0
  63. package/routes/admin/auto/migrations.json +21 -0
  64. package/routes/admin/auto/users.json +61 -0
  65. package/routes/admin/middlewares/index.js +87 -0
  66. package/routes/admin/spec/auth.js +626 -0
  67. package/routes/admin/spec/users.js +3 -0
  68. package/routes/auto/handler.js +635 -0
  69. package/routes/common/auto/countries.json +28 -0
  70. package/routes/common/auto/currencies.json +26 -0
  71. package/routes/common/auto/languages.json +26 -0
  72. package/routes/common/auto/taxes.json +46 -0
  73. package/routes/common/auto/timezones.json +29 -0
  74. package/stores/base.js +73 -0
  75. package/stores/index.js +195 -0
  76. package/utils/i18n.js +187 -0
  77. package/utils/jsonRouteLoader.js +587 -0
  78. package/utils/middleware.js +154 -0
  79. package/utils/routeLoader.js +227 -0
  80. package/workers/admin.js +124 -0
  81. package/workers/api.js +106 -0
@@ -0,0 +1,52 @@
1
+ {
2
+ "validation": {
3
+ "required": "El campo '{field}' es requerido",
4
+ "minLength": "El campo '{field}' debe tener al menos {min} caracteres",
5
+ "maxLength": "El campo '{field}' debe tener como máximo {max} caracteres",
6
+ "min": "El campo '{field}' debe ser al menos {min}",
7
+ "max": "El campo '{field}' debe ser como máximo {max}",
8
+ "email": "El campo '{field}' debe ser una dirección de correo válida",
9
+ "integer": "El campo '{field}' debe ser un entero",
10
+ "string": "El campo '{field}' debe ser una cadena de texto",
11
+ "boolean": "El campo '{field}' debe ser un booleano",
12
+ "enum": "El campo '{field}' debe ser uno de: {enum}",
13
+ "format": "El formato del campo '{field}' es inválido",
14
+ "pattern": "El campo '{field}' no coincide con el patrón requerido",
15
+ "unique": "El campo '{field}' debe ser único",
16
+ "exists": "El campo '{field}' no existe",
17
+ "invalid": "El campo '{field}' es inválido",
18
+ "tooShort": "El campo '{field}' es muy corto",
19
+ "tooLong": "El campo '{field}' es muy largo",
20
+ "invalidEmail": "Por favor proporcione una dirección de correo válida",
21
+ "weakPassword": "La contraseña debe tener al menos 6 caracteres",
22
+ "invalidRole": "El rol debe ser uno de: admin, user, moderator"
23
+ },
24
+ "errors": {
25
+ "validation": "Error de Validación",
26
+ "server": "Error del Servidor",
27
+ "notFound": "Recurso no encontrado",
28
+ "unauthorized": "Acceso no autorizado",
29
+ "forbidden": "Acceso prohibido",
30
+ "conflict": "Conflicto de recurso",
31
+ "badRequest": "Solicitud incorrecta"
32
+ },
33
+ "messages": {
34
+ "userCreated": "Usuario creado exitosamente",
35
+ "userUpdated": "Usuario actualizado exitosamente",
36
+ "userDeleted": "Usuario eliminado exitosamente",
37
+ "userNotFound": "Usuario no encontrado",
38
+ "emailExists": "El correo ya existe",
39
+ "invalidCredentials": "Credenciales inválidas",
40
+ "accessDenied": "Acceso denegado",
41
+ "languageChanged": "Idioma cambiado a {language}",
42
+ "adminCreated": "Administrador creado exitosamente",
43
+ "adminUpdated": "Administrador actualizado exitosamente",
44
+ "adminDeleted": "Administrador eliminado exitosamente",
45
+ "adminNotFound": "Administrador no encontrado",
46
+ "twoFactorEnabled": "Autenticación de dos factores habilitada",
47
+ "twoFactorDisabled": "Autenticación de dos factores deshabilitada",
48
+ "phoneVerified": "Número de teléfono verificado exitosamente",
49
+ "invalidVerificationCode": "Código de verificación inválido",
50
+ "verificationCodeExpired": "El código de verificación ha expirado"
51
+ }
52
+ }
@@ -0,0 +1,59 @@
1
+ {
2
+ "required": "{field} alanı zorunludur",
3
+ "minLength": "{field} alanı en az {min} karakter olmalıdır",
4
+ "maxLength": "{field} alanı en fazla {max} karakter olmalıdır",
5
+ "min": "{field} alanı en az {min} olmalıdır",
6
+ "max": "{field} alanı en fazla {max} olmalıdır",
7
+ "email": "{field} alanı geçerli bir e-posta adresi olmalıdır",
8
+ "integer": "{field} alanı bir tam sayı olmalıdır",
9
+ "string": "{field} alanı bir metin olmalıdır",
10
+ "boolean": "{field} alanı bir boolean değer olmalıdır",
11
+ "enum": "{field} alanı şunlardan biri olmalıdır: {enum}",
12
+ "format": "{field} alanının formatı geçersizdir",
13
+ "pattern": "{field} alanı gerekli desenle eşleşmiyor",
14
+ "unique": "{field} alanı benzersiz olmalıdır",
15
+ "exists": "{field} alanı mevcut değil",
16
+ "invalid": "{field} alanı geçersizdir",
17
+ "tooShort": "{field} alanı çok kısa",
18
+ "tooLong": "{field} alanı çok uzun",
19
+ "invalidEmail": "Lütfen geçerli bir e-posta adresi girin",
20
+ "weakPassword": "Şifre en az 6 karakter olmalıdır",
21
+ "invalidRole": "Rol şunlardan biri olmalıdır: admin, user, moderator",
22
+ "errors": {
23
+ "validation": "Doğrulama Hatası",
24
+ "server": "Sunucu Hatası",
25
+ "notFound": "Kaynak bulunamadı",
26
+ "unauthorized": "Yetkisiz erişim",
27
+ "forbidden": "Erişim yasak",
28
+ "conflict": "Kaynak çakışması",
29
+ "badRequest": "Hatalı İstek"
30
+ },
31
+ "fields": {
32
+ "username": "Kullanıcı Adı",
33
+ "password": "Şifre",
34
+ "email": "E-posta",
35
+ "phone": "Telefon",
36
+ "name": "Ad",
37
+ "surname": "Soyad",
38
+ "type": "Tür"
39
+ },
40
+ "messages": {
41
+ "userCreated": "Kullanıcı başarıyla oluşturuldu",
42
+ "userUpdated": "Kullanıcı başarıyla güncellendi",
43
+ "userDeleted": "Kullanıcı başarıyla silindi",
44
+ "userNotFound": "Kullanıcı bulunamadı",
45
+ "emailExists": "E-posta zaten mevcut",
46
+ "invalidCredentials": "Geçersiz kimlik bilgileri",
47
+ "accessDenied": "Erişim reddedildi",
48
+ "languageChanged": "Dil {language} olarak değiştirildi",
49
+ "adminCreated": "Yönetici başarıyla oluşturuldu",
50
+ "adminUpdated": "Yönetici başarıyla güncellendi",
51
+ "adminDeleted": "Yönetici başarıyla silindi",
52
+ "adminNotFound": "Yönetici bulunamadı",
53
+ "twoFactorEnabled": "İki faktörlü kimlik doğrulama etkinleştirildi",
54
+ "twoFactorDisabled": "İki faktörlü kimlik doğrulama devre dışı bırakıldı",
55
+ "phoneVerified": "Telefon numarası başarıyla doğrulandı",
56
+ "invalidVerificationCode": "Geçersiz doğrulama kodu",
57
+ "verificationCodeExpired": "Doğrulama kodu süresi dolmuş"
58
+ }
59
+ }
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "@ecopex/ecopex-framework",
3
+ "version": "1.0.0",
4
+ "description": "Javascript Framework for API and Admin Panel",
5
+ "main": "ecosystem.config.js",
6
+ "scripts": {
7
+ "start": "pm2 start ecosystem.config.js",
8
+ "restart": "pm2 restart ecosystem.config.js",
9
+ "delete": "pm2 delete ecosystem.config.js",
10
+ "logs": "pm2 logs",
11
+ "status": "pm2 status",
12
+ "dev": "pm2 start ecosystem.config.js --env development",
13
+ "prod": "pm2 start ecosystem.config.js --env production",
14
+ "migrate": "knex migrate:latest",
15
+ "migrate:rollback": "knex migrate:rollback",
16
+ "seed": "knex seed:run"
17
+ },
18
+ "dependencies": {
19
+ "@aws-sdk/client-s3": "^3.958.0",
20
+ "@aws-sdk/lib-storage": "^3.958.0",
21
+ "@clickhouse/client": "^1.12.1",
22
+ "@fastify/cors": "^11.2.0",
23
+ "@fastify/multipart": "^9.3.0",
24
+ "@fastify/swagger": "9.6.1",
25
+ "@fastify/swagger-ui": "5.2.3",
26
+ "@questdb/nodejs-client": "^4.0.2",
27
+ "ajv": "^8.12.0",
28
+ "ajv-formats": "^2.1.1",
29
+ "axios": "^1.12.2",
30
+ "bcrypt": "^5.1.1",
31
+ "bcryptjs": "^3.0.2",
32
+ "dayjs": "^1.11.18",
33
+ "dotenv": "^16.3.1",
34
+ "fastify": "^5.6.2",
35
+ "image-size": "^2.0.2",
36
+ "jsonwebtoken": "^9.0.2",
37
+ "knex": "^3.0.1",
38
+ "module-alias": "^2.2.3",
39
+ "mysql2": "^3.6.5",
40
+ "node-2fa": "^2.0.3",
41
+ "node-device-detector": "^2.2.4",
42
+ "node-image-resizer": "^1.0.0",
43
+ "pg": "^8.16.3",
44
+ "pm2": "^5.3.0",
45
+ "qrcode": "^1.5.4",
46
+ "slugify": "^1.6.6",
47
+ "uuid": "^9.0.1"
48
+ },
49
+ "devDependencies": {
50
+ "nodemon": "^3.0.1",
51
+ "pino-pretty": "^13.1.1"
52
+ },
53
+ "keywords": [
54
+ "fastify",
55
+ "knex",
56
+ "ajv",
57
+ "nodejs",
58
+ "api",
59
+ "mysql",
60
+ "pm2"
61
+ ],
62
+ "author": "Kubilay Hazır",
63
+ "license": "MIT",
64
+ "directories": {
65
+ "test": "tests"
66
+ },
67
+ "repository": {
68
+ "type": "git",
69
+ "url": "git+ssh://git@github.com/kubilayhazir/ecopex.git"
70
+ },
71
+ "bugs": {
72
+ "url": "https://github.com/kubilayhazir/ecopex/issues"
73
+ },
74
+ "homepage": "https://github.com/kubilayhazir/ecopex#readme"
75
+ }
@@ -0,0 +1,63 @@
1
+ {
2
+ "table": "admins",
3
+ "primary_key": "admin_id",
4
+ "schema": {
5
+ "admin_id": { "type": "integer" },
6
+ "avatar": { "type": "string", "default": null },
7
+ "username": { "type": "string", "default": "", "search_type": "equal", "generatable": "email", "updatable": true, "unique": true },
8
+ "email": { "type": "string", "format": "email", "default": null, "search_type": "equal", "creatable": true, "create_required": true },
9
+ "phone_verified": { "type": "boolean", "default": false, "search_type": "equal" },
10
+ "password": { "type": "string", "default": "", "creatable": true, "listable": false, "create_required": true, "crypted": true },
11
+ "email_verified": { "type": "boolean", "search_type": "equal" },
12
+ "role": { "type": "string", "enum": ["admin", "support"], "search_type": "equal" },
13
+ "firstname": { "type": "string", "default": "", "search_type": "like", "creatable": true, "create_required": true, "updatable": true },
14
+ "lastname": { "type": "string", "default": "", "search_type": "like", "creatable": true, "create_required": true, "updatable": true },
15
+ "phone_number": { "type": "string", "search_type": "like", "creatable": true, "updatable": true },
16
+ "two_factor_enabled": { "type": "boolean", "default": false, "search_type": "equal", "updatable": true },
17
+ "is_active": { "type": "boolean", "default": true, "search_type": "equal", "updatable": true },
18
+ "login_attempts": { "type": "integer", "default": 0, "search_type": "equal", "updatable": true },
19
+ "locked_until": { "type": "string", "format": "date", "default": null, "search_type": "like", "updatable": true },
20
+ "created_at": { "type": "string", "format": "date-time", "default": null, "search_type": "like" },
21
+ "updated_at": { "type": "string", "format": "date-time", "default": null, "search_type": "like" }
22
+ },
23
+ "routes": [
24
+ {
25
+ "action": "list",
26
+ "method": "GET",
27
+ "path": "/admins",
28
+ "searchable_fields": ["username", "email"],
29
+ "pagination": true,
30
+ "additionalProperties": false,
31
+ "security": "auth"
32
+ },
33
+ {
34
+ "action": "get",
35
+ "method": "GET",
36
+ "path": "/admins/:admin_id",
37
+ "security": "auth",
38
+ "additionalProperties": false
39
+ },
40
+ {
41
+ "action": "create",
42
+ "method": "POST",
43
+ "path": "/admins",
44
+ "security": "auth",
45
+ "additionalProperties": false
46
+ },
47
+ {
48
+ "action": "update",
49
+ "method": "PUT",
50
+ "path": "/admins/:admin_id",
51
+ "security": "auth",
52
+ "additionalProperties": false
53
+ },
54
+ {
55
+ "action": "upload",
56
+ "method": "POST",
57
+ "path": "/admins/:admin_id/upload",
58
+ "security": "auth",
59
+ "upload_field": "avatar",
60
+ "additionalProperties": false
61
+ }
62
+ ]
63
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "table": "devices",
3
+ "primary_key": "device_id",
4
+ "schema": {
5
+ "device_id": { "type": "integer" },
6
+ "personal_id": { "type": "integer" },
7
+ "personal_type": { "type": "string", "enum": ["admin", "manager", "user"] },
8
+ "unique_id": { "type": "string", "default": "" },
9
+ "ip_address": { "type": "string", "default": "" },
10
+ "device_information": { "type": "string", "default": "" },
11
+ "two_factor_approved": { "type": "boolean", "default": false },
12
+ "token": { "type": "string", "default": "" },
13
+ "last_login_at": { "type": "string", "format": "date-time", "default": null },
14
+ "last_login_ip": { "type": "string", "default": null },
15
+ "created_at": { "type": "string", "format": "date-time", "default": null },
16
+ "updated_at": { "type": "string", "format": "date-time", "default": null }
17
+ },
18
+ "routes": [
19
+ {
20
+ "action": "list",
21
+ "method": "GET",
22
+ "path": "/devices",
23
+ "searchable_fields": ["unique_id", "ip_address", "device_information"],
24
+ "pagination": true,
25
+ "additionalProperties": false,
26
+ "security": "auth",
27
+ "owned": "personal_id",
28
+ "owned_type": "personal_type"
29
+ },
30
+ {
31
+ "action": "get",
32
+ "method": "GET",
33
+ "path": "/devices/:admin_id",
34
+ "security": "auth"
35
+ }
36
+ ]
37
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "table": "knex_migrations",
3
+ "primary_key": "id",
4
+ "schema": {
5
+ "id": { "type": "integer" },
6
+ "name": { "type": "string" },
7
+ "batch": { "type": "integer" },
8
+ "migration_time": { "type": "string", "format": "date-time" }
9
+ },
10
+ "routes": [
11
+ {
12
+ "action": "list",
13
+ "method": "GET",
14
+ "path": "/migrations",
15
+ "searchable_fields": ["name"],
16
+ "pagination": true,
17
+ "additionalProperties": false,
18
+ "security": "auth"
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,61 @@
1
+ {
2
+ "table": "users",
3
+ "primary_key": "user_id",
4
+ "schema": {
5
+ "user_id": { "type": "integer" },
6
+ "username": { "type": "string", "default": "", "search_type": "equal" },
7
+ "email": { "type": "string", "format": "email", "default": null, "search_type": "equal" },
8
+ "type_user": { "type": "string", "enum": ["vip", "new", "risky"], "search_type": "equal" },
9
+ "manager_id": { "type": "integer", "nullable": true, "search_type": "equal" },
10
+ "phone_verified": { "type": "boolean", "default": false, "search_type": "equal" },
11
+ "email_verified": { "type": "boolean", "default": false, "search_type": "equal" },
12
+ "phone_number": { "type": "string", "nullable": true, "search_type": "like" },
13
+ "two_factor_enabled": { "type": "boolean", "default": false, "search_type": "equal" },
14
+ "is_active": { "type": "boolean", "default": true, "search_type": "equal" },
15
+ "login_attempts": { "type": "integer", "default": 0, "nullable": true, "search_type": "equal" },
16
+ "locked_until": { "type": "string", "format": "date-time", "default": null, "nullable": true, "search_type": "like" },
17
+ "created_at": { "type": "string", "format": "date-time", "default": null, "search_type": "like" },
18
+ "updated_at": { "type": "string", "format": "date-time", "default": null, "search_type": "like" },
19
+ "manager": {
20
+ "type": ["object", "null"],
21
+ "properties": {
22
+ "manager_id": { "type": "integer" },
23
+ "username": { "type": "string" },
24
+ "email": { "type": "string", "format": "email", "default": null }
25
+ }
26
+ }
27
+ },
28
+ "routes": [
29
+ {
30
+ "action": "list",
31
+ "method": "GET",
32
+ "path": "/users",
33
+ "searchable_fields": ["username", "email"],
34
+ "pagination": true,
35
+ "additionalProperties": false,
36
+ "security": "auth"
37
+ },
38
+ {
39
+ "action": "get",
40
+ "method": "GET",
41
+ "path": "/users/:user_id",
42
+ "searchable_fields": ["username", "email"],
43
+ "security": "auth",
44
+ "additionalProperties": false
45
+ },
46
+ {
47
+ "action": "create",
48
+ "method": "POST",
49
+ "path": "/users",
50
+ "security": "auth",
51
+ "additionalProperties": false
52
+ },
53
+ {
54
+ "action": "update",
55
+ "method": "PUT",
56
+ "path": "/users/:user_id",
57
+ "security": "auth",
58
+ "additionalProperties": false
59
+ }
60
+ ]
61
+ }
@@ -0,0 +1,87 @@
1
+ const JWT = require('@root/libraries/jwt');
2
+ const db = require('@root/libraries/knex');
3
+ const jwt = new JWT();
4
+
5
+ const auth = async (request, reply) => {
6
+
7
+ const { authorization } = request.headers;
8
+ const activePath = request.routeOptions?.url?.replace('/', '').replace(/\//g, '.') || 'unknown';
9
+
10
+ if(!authorization) {
11
+ return reply.status(401).send({
12
+ status: false,
13
+ message: 'Unauthorized',
14
+ code: 401
15
+ });
16
+ }
17
+
18
+ const token = authorization.split(' ')[1];
19
+ if(!token) {
20
+ return reply.status(401).send({
21
+ status: false,
22
+ message: 'Unauthorized',
23
+ code: 401
24
+ });
25
+ }
26
+
27
+ const decoded = jwt.verify(token);
28
+
29
+ if(!decoded) {
30
+ return reply.status(401).send({
31
+ status: false,
32
+ message: 'Token is invalid',
33
+ code: 401
34
+ });
35
+ }
36
+
37
+ let user = null;
38
+
39
+ if(decoded.type === 'admin') {
40
+ user = await db('admins').where('admin_id', decoded.id).first();
41
+ } else if(decoded.type === 'manager') {
42
+ user = await db('managers').where('manager_id', decoded.id).first();
43
+ }
44
+
45
+ if(!user) {
46
+ return reply.status(401).send({
47
+ status: false,
48
+ message: 'Unauthorized',
49
+ code: 401
50
+ });
51
+ }
52
+
53
+ user.id = user.admin_id || user.manager_id;
54
+ user.type = decoded.type;
55
+
56
+ const device = await db('devices').where({ personal_id: user.id, personal_type: user.type, unique_id: decoded.device || '--' }).first();
57
+
58
+ if(!device) {
59
+ return reply.status(401).send({
60
+ status: false,
61
+ message: 'Unauthorized',
62
+ code: 401
63
+ });
64
+ }
65
+
66
+ if(user.two_factor_enabled && !device.two_factor_approved && activePath !== 'auth.login-2fa') {
67
+ return reply.status(303).send({
68
+ status: false,
69
+ message: 'Device is not approved',
70
+ details: {
71
+ token: device.token,
72
+ two_factor_approved: device.two_factor_approved
73
+ },
74
+ code: 303
75
+ });
76
+ }
77
+
78
+ delete user.admin_id;
79
+ delete user.manager_id;
80
+
81
+ request.user = user;
82
+
83
+ }
84
+
85
+ module.exports = {
86
+ auth
87
+ }