@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.
- package/.env +73 -0
- package/README.md +248 -0
- package/bun.lockb +0 -0
- package/config/swagger/admin.js +44 -0
- package/config/swagger/api.js +19 -0
- package/database/migrations/20240000135243_timezones.js +22 -0
- package/database/migrations/20240000135244_countries.js +23 -0
- package/database/migrations/20240000135244_create_admins_table.js +66 -0
- package/database/migrations/20240000135244_currencies.js +21 -0
- package/database/migrations/20240000135244_languages.js +21 -0
- package/database/migrations/20240000135244_taxes.js +10 -0
- package/database/migrations/20240000135245_sites.js +37 -0
- package/database/migrations/20240000135246_payment_methods.js +33 -0
- package/database/migrations/20251016113547_devices.js +37 -0
- package/database/migrations/20251019192600_users.js +62 -0
- package/database/migrations/20251019213551_language_lines.js +35 -0
- package/database/migrations/20251222214131_category_groups.js +18 -0
- package/database/migrations/20251222214619_categories.js +27 -0
- package/database/migrations/20251222214848_brands.js +23 -0
- package/database/migrations/20251222214946_products.js +30 -0
- package/database/migrations/20251222215428_product_images.js +18 -0
- package/database/migrations/20251222215553_options.js +30 -0
- package/database/migrations/20251222215806_variants.js +23 -0
- package/database/migrations/20251222215940_attributes.js +25 -0
- package/database/migrations/20251222220135_discounts.js +15 -0
- package/database/migrations/20251222220253_reviews.js +22 -0
- package/database/migrations/20251222220341_favorites.js +10 -0
- package/database/migrations/20251222220422_search_logs.js +17 -0
- package/database/migrations/20251222220636_orders.js +16 -0
- package/database/migrations/20251222220806_order_items.js +19 -0
- package/database/migrations/20251222221317_order_statuses.js +10 -0
- package/database/migrations/20251222221446_order_payments.js +13 -0
- package/database/migrations/20251222221654_order_addresses.js +23 -0
- package/database/migrations/20251222221807_order_status_logs.js +13 -0
- package/database/seeds/admins.js +37 -0
- package/database/seeds/countries.js +203 -0
- package/database/seeds/currencies.js +165 -0
- package/database/seeds/languages.js +113 -0
- package/database/seeds/timezones.js +149 -0
- package/ecosystem.config.js +26 -0
- package/env.example +73 -0
- package/knexfile.js +3 -0
- package/libraries/2fa.js +22 -0
- package/libraries/aws.js +63 -0
- package/libraries/bcrypt.js +284 -0
- package/libraries/controls.js +113 -0
- package/libraries/date.js +14 -0
- package/libraries/general.js +8 -0
- package/libraries/image.js +57 -0
- package/libraries/jwt.js +178 -0
- package/libraries/knex.js +7 -0
- package/libraries/slug.js +14 -0
- package/libraries/stores.js +22 -0
- package/libraries/upload.js +194 -0
- package/locales/en/messages.json +4 -0
- package/locales/en/sql.json +3 -0
- package/locales/en/validation.json +52 -0
- package/locales/es/validation.json +52 -0
- package/locales/tr/validation.json +59 -0
- package/package.json +75 -0
- package/routes/admin/auto/admins.json +63 -0
- package/routes/admin/auto/devices.json +37 -0
- package/routes/admin/auto/migrations.json +21 -0
- package/routes/admin/auto/users.json +61 -0
- package/routes/admin/middlewares/index.js +87 -0
- package/routes/admin/spec/auth.js +626 -0
- package/routes/admin/spec/users.js +3 -0
- package/routes/auto/handler.js +635 -0
- package/routes/common/auto/countries.json +28 -0
- package/routes/common/auto/currencies.json +26 -0
- package/routes/common/auto/languages.json +26 -0
- package/routes/common/auto/taxes.json +46 -0
- package/routes/common/auto/timezones.json +29 -0
- package/stores/base.js +73 -0
- package/stores/index.js +195 -0
- package/utils/i18n.js +187 -0
- package/utils/jsonRouteLoader.js +587 -0
- package/utils/middleware.js +154 -0
- package/utils/routeLoader.js +227 -0
- package/workers/admin.js +124 -0
- package/workers/api.js +106 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
const i18n = require('./i18n');
|
|
2
|
+
const db = require('@root/libraries/knex');
|
|
3
|
+
/**
|
|
4
|
+
* Middleware utilities for Fastify
|
|
5
|
+
*/
|
|
6
|
+
class Middleware {
|
|
7
|
+
/**
|
|
8
|
+
* Language detection middleware
|
|
9
|
+
* Adds detected language to request object
|
|
10
|
+
*/
|
|
11
|
+
static languageDetection() {
|
|
12
|
+
return async (request, reply) => {
|
|
13
|
+
// Detect language from request
|
|
14
|
+
const locale = i18n.detectLanguage(request);
|
|
15
|
+
|
|
16
|
+
// Add locale to request object
|
|
17
|
+
request.locale = locale;
|
|
18
|
+
request.t = (key, params = {}) => i18n.t(key, locale, params);
|
|
19
|
+
|
|
20
|
+
const language = await db('languages').where('code', locale).first();
|
|
21
|
+
|
|
22
|
+
request.language = language ? language.language_id : 1;
|
|
23
|
+
|
|
24
|
+
// Add locale to reply headers
|
|
25
|
+
reply.header('Content-Language', locale);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Error handler middleware with custom response format
|
|
31
|
+
*/
|
|
32
|
+
static errorHandler(error, request, reply) {
|
|
33
|
+
|
|
34
|
+
if(process.env.NODE_ENV === 'development') {
|
|
35
|
+
console.log(error);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const locale = request.locale || i18n.detectLanguage(request);
|
|
39
|
+
|
|
40
|
+
if(error.data?.sqlMessage) {
|
|
41
|
+
return sqlErrorHandler(error, request, reply);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Handle validation errors
|
|
45
|
+
if (error.validation) {
|
|
46
|
+
const validationErrors = error.validation.map(err => {
|
|
47
|
+
const field_name = err.instancePath?.replace('/', '') || err.dataPath?.replace('/', '') || err.params?.missingProperty || 'field'
|
|
48
|
+
return {
|
|
49
|
+
field: field_name,
|
|
50
|
+
message: i18n.t('validation.' + err.keyword, locale, { field: i18n.t('validation.fields.' + field_name, locale) })
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return reply.status(400).send({
|
|
55
|
+
status: false,
|
|
56
|
+
message: i18n.t('validation.errors.validation', locale),
|
|
57
|
+
details: validationErrors,
|
|
58
|
+
code: 400
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Handle 404 Not Found errors
|
|
63
|
+
if (error.statusCode === 404) {
|
|
64
|
+
return reply.status(404).send({
|
|
65
|
+
status: false,
|
|
66
|
+
message: error.message || 'Route not found',
|
|
67
|
+
code: 404
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Handle other errors
|
|
72
|
+
const statusCode = error.statusCode || 500;
|
|
73
|
+
|
|
74
|
+
reply.status(statusCode).send({
|
|
75
|
+
status: false,
|
|
76
|
+
message: error.message || 'Internal server error',
|
|
77
|
+
code: statusCode
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static errorNotFoundHandler(request, reply) {
|
|
82
|
+
reply.status(404).send({
|
|
83
|
+
status: false,
|
|
84
|
+
message: request.method + ':' + request.url + ' not found',
|
|
85
|
+
code: 404
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get error key based on status code
|
|
91
|
+
* @param {number} statusCode - HTTP status code
|
|
92
|
+
* @returns {string} Error key
|
|
93
|
+
*/
|
|
94
|
+
static getErrorKey(statusCode) {
|
|
95
|
+
switch (statusCode) {
|
|
96
|
+
case 400:
|
|
97
|
+
return 'errors.badRequest';
|
|
98
|
+
case 401:
|
|
99
|
+
return 'errors.unauthorized';
|
|
100
|
+
case 403:
|
|
101
|
+
return 'errors.forbidden';
|
|
102
|
+
case 404:
|
|
103
|
+
return 'errors.notFound';
|
|
104
|
+
case 409:
|
|
105
|
+
return 'errors.conflict';
|
|
106
|
+
case 500:
|
|
107
|
+
default:
|
|
108
|
+
return 'errors.server';
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Response formatter middleware
|
|
114
|
+
* Adds consistent response format with i18n support
|
|
115
|
+
*/
|
|
116
|
+
static responseFormatter() {
|
|
117
|
+
return async (request, reply) => {
|
|
118
|
+
|
|
119
|
+
// Skip response formatting for Swagger routes
|
|
120
|
+
if (request.routeOptions.url.startsWith('/docs') || request.routeOptions.url.startsWith('/docs/')) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const originalSend = reply.send;
|
|
125
|
+
|
|
126
|
+
reply.send = function(payload) {
|
|
127
|
+
// If payload is already formatted (success or error), send as is
|
|
128
|
+
if (payload && typeof payload === 'object' &&
|
|
129
|
+
(payload.success !== undefined || payload.status !== undefined || payload.error !== undefined)) {
|
|
130
|
+
return originalSend.call(this, payload);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Format success response
|
|
134
|
+
const locale = request.locale || 'en';
|
|
135
|
+
return originalSend.call(this, {
|
|
136
|
+
status: true,
|
|
137
|
+
data: payload,
|
|
138
|
+
locale,
|
|
139
|
+
timestamp: new Date().toISOString()
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const sqlErrorHandler = (error, request, reply) => {
|
|
148
|
+
return reply.status(500).send({
|
|
149
|
+
status: false,
|
|
150
|
+
message: i18n.t('sql.' + error.data.code.toLowerCase(), request.headers.locale || 'en', {...request.body, ...request.query, ...request.params})
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
module.exports = Middleware;
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const JsonRouteLoader = require('./jsonRouteLoader');
|
|
4
|
+
const general = require('@root/libraries/general');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Automatically loads routes from the routes directory
|
|
8
|
+
* @param {Object} fastify - Fastify instance
|
|
9
|
+
* @param {string} routesDir - Path to routes directory
|
|
10
|
+
* @param {boolean} includeCommon - Whether to include common routes
|
|
11
|
+
* @param {boolean} includeAuto - Whether to include auto routes
|
|
12
|
+
*/
|
|
13
|
+
async function loadRoutes(fastify, routesDir = './routes', includeCommon = false, workerName) {
|
|
14
|
+
const routesPath = path.resolve(routesDir);
|
|
15
|
+
|
|
16
|
+
if (!fs.existsSync(routesPath)) {
|
|
17
|
+
console.warn(`Routes directory ${routesPath} does not exist`);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if(!fs.existsSync(routesPath + '/spec')) {
|
|
22
|
+
fs.mkdirSync(routesPath + '/spec');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Load routes from each subdirectory (admin, api, etc.)
|
|
26
|
+
const routeGroups = fs.readdirSync(routesPath + '/spec', { withFileTypes: true })
|
|
27
|
+
.filter(dirent => {
|
|
28
|
+
return dirent.name.includes('.js') && dirent.isFile();
|
|
29
|
+
})
|
|
30
|
+
.map(dirent => dirent.name);
|
|
31
|
+
|
|
32
|
+
for (const group of routeGroups) {
|
|
33
|
+
const groupPath = path.join(routesPath + '/spec', group);
|
|
34
|
+
await loadRouteGroup(fastify, group, groupPath, workerName);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Load JSON routes from routes/admin folder
|
|
38
|
+
const jsonLoader = new JsonRouteLoader();
|
|
39
|
+
await jsonLoader.loadJsonRoutes(fastify, routesPath + '/auto', workerName);
|
|
40
|
+
|
|
41
|
+
// // Load common routes if requested
|
|
42
|
+
if (includeCommon) {
|
|
43
|
+
const commonPath = path.resolve(__dirname + '/../routes/common');
|
|
44
|
+
|
|
45
|
+
if (fs.existsSync(commonPath)) {
|
|
46
|
+
|
|
47
|
+
if(!fs.existsSync(commonPath + '/spec')) {
|
|
48
|
+
fs.mkdirSync(commonPath + '/spec');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const routeGroupsCommon = fs.readdirSync(commonPath + '/spec', { withFileTypes: true })
|
|
52
|
+
.filter(dirent => {
|
|
53
|
+
return dirent.name.includes('.js') && dirent.isFile();
|
|
54
|
+
})
|
|
55
|
+
.map(dirent => dirent.name);
|
|
56
|
+
|
|
57
|
+
for (const group of routeGroupsCommon) {
|
|
58
|
+
const groupPath = path.join(commonPath + '/spec', group);
|
|
59
|
+
await loadRouteGroup(fastify, group, groupPath, workerName);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
await jsonLoader.loadJsonRoutes(fastify, commonPath + '/auto', workerName);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Load routes from a specific group (admin, api, etc.)
|
|
69
|
+
* @param {Object} fastify - Fastify instance
|
|
70
|
+
* @param {string} groupName - Name of the route group
|
|
71
|
+
* @param {string} groupPath - Path to the route group directory
|
|
72
|
+
*/
|
|
73
|
+
async function loadRouteGroup(fastify, groupName, groupPath, workerName) {
|
|
74
|
+
await loadRouteModule(fastify, groupName, groupPath, workerName);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Load a specific route module
|
|
79
|
+
* @param {Object} fastify - Fastify instance
|
|
80
|
+
* @param {string} groupName - Name of the route group
|
|
81
|
+
* @param {string} moduleName - Name of the route module
|
|
82
|
+
* @param {string} modulePath - Path to the route module directory
|
|
83
|
+
*/
|
|
84
|
+
async function loadRouteModule(fastify, groupName, modulePath, workerName) {
|
|
85
|
+
|
|
86
|
+
// Check if route file exists
|
|
87
|
+
if (!fs.existsSync(modulePath)) {
|
|
88
|
+
console.warn(`Route file not found: ${modulePath}`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const moduleName = modulePath.split('/').pop().split('.')[0];
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
|
|
95
|
+
// Load route configuration
|
|
96
|
+
const moduleFile = require(modulePath);
|
|
97
|
+
|
|
98
|
+
if(Array.isArray(moduleFile)) {
|
|
99
|
+
|
|
100
|
+
for (const route of moduleFile) {
|
|
101
|
+
if(!route.method) {
|
|
102
|
+
console.warn(`Route method is required for route ${route.path}`);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if(!route.handler) {
|
|
106
|
+
console.warn(`Route handler is required for route ${route.path}`);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if(route.path) {
|
|
111
|
+
route.path = `/${moduleName}/${route.path}`;
|
|
112
|
+
} else {
|
|
113
|
+
route.path = `/${moduleName}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
await registerRoute(fastify, route, moduleName, workerName);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.error(`Error loading route module ${modulePath}:`, error);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Register a single route
|
|
127
|
+
* @param {Object} fastify - Fastify instance
|
|
128
|
+
* @param {Object} routeConfig - Route configuration
|
|
129
|
+
* @param {Object} handler - Route handler
|
|
130
|
+
* @param {Object} validation - Route validation
|
|
131
|
+
* @param {string} prefix - Route prefix
|
|
132
|
+
*/
|
|
133
|
+
async function registerRoute(fastify, routeConfig, moduleName, workerName) {
|
|
134
|
+
const {
|
|
135
|
+
method = 'GET',
|
|
136
|
+
path = '',
|
|
137
|
+
schema = {},
|
|
138
|
+
preHandler = undefined,
|
|
139
|
+
handler = () => {},
|
|
140
|
+
security = undefined
|
|
141
|
+
} = routeConfig;
|
|
142
|
+
|
|
143
|
+
// Build full URL
|
|
144
|
+
const fullUrl = path;
|
|
145
|
+
|
|
146
|
+
if(schema.success_response && schema.success_response.type === 'object' && schema.success_response.properties && schema.success_response.properties.pagination) {
|
|
147
|
+
schema.success_response.properties.pagination = {
|
|
148
|
+
type: 'object',
|
|
149
|
+
properties: {
|
|
150
|
+
total: { type: 'integer' },
|
|
151
|
+
page: { type: 'integer' },
|
|
152
|
+
limit: { type: 'integer' },
|
|
153
|
+
totalPages: { type: 'integer' }
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const schemaRoute = {
|
|
159
|
+
tags: [general.titleCase(moduleName)],
|
|
160
|
+
response: {
|
|
161
|
+
200: schema.success_response ? { ...schema.success_response, additionalProperties: schema.success_response.additionalProperties || false } : {
|
|
162
|
+
type: 'object',
|
|
163
|
+
properties: {
|
|
164
|
+
status: { type: 'boolean', default: true },
|
|
165
|
+
data: { type: 'object' }
|
|
166
|
+
},
|
|
167
|
+
additionalProperties: false
|
|
168
|
+
},
|
|
169
|
+
400: schema.error_response || {
|
|
170
|
+
type: 'object',
|
|
171
|
+
properties: {
|
|
172
|
+
status: { type: 'boolean', default: false },
|
|
173
|
+
message: { type: 'string' },
|
|
174
|
+
details: { type: 'array', items: { type: 'object', properties: { field: { type: 'string' }, message: { type: 'string' } } } }
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if(schema.body) {
|
|
181
|
+
schemaRoute.body = schema.body;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if(schema.params) {
|
|
185
|
+
schemaRoute.params = schema.params;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if(schema.headers) {
|
|
189
|
+
schemaRoute.headers = schema.headers;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if(schema.querystring) {
|
|
193
|
+
schemaRoute.querystring = schema.querystring;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if(schema.security) {
|
|
197
|
+
schemaRoute.security = schema.security;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if(schema.summary) {
|
|
201
|
+
schemaRoute.summary = schema.summary;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if(schema.description) {
|
|
205
|
+
schemaRoute.description = schema.description;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if(security && security === 'auth') {
|
|
209
|
+
schemaRoute.security = [{ bearer: [] }];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Register the route
|
|
213
|
+
fastify.route({
|
|
214
|
+
method: method.toUpperCase(),
|
|
215
|
+
url: fullUrl,
|
|
216
|
+
schema: schemaRoute,
|
|
217
|
+
preHandler: async (request, reply, done) => {
|
|
218
|
+
|
|
219
|
+
if(security) {
|
|
220
|
+
await require(`@root/routes/${workerName}/middlewares`)[security](request, reply);
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
handler: handler
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
module.exports = { loadRoutes };
|
package/workers/admin.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
require('module-alias/register')
|
|
2
|
+
require('dotenv').config();
|
|
3
|
+
|
|
4
|
+
const Store = require('@root/libraries/stores');
|
|
5
|
+
|
|
6
|
+
const fastify = require('fastify')({
|
|
7
|
+
logger: {
|
|
8
|
+
level: process.env.LOG_LEVEL || 'info'
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const { loadRoutes } = require('@root/utils/routeLoader');
|
|
13
|
+
const Middleware = require('@root/utils/middleware');
|
|
14
|
+
|
|
15
|
+
// Register plugins
|
|
16
|
+
async function registerPlugins() {
|
|
17
|
+
// Set error handler first, before any plugins
|
|
18
|
+
fastify.setErrorHandler(Middleware.errorHandler);
|
|
19
|
+
fastify.setNotFoundHandler(Middleware.errorNotFoundHandler);
|
|
20
|
+
|
|
21
|
+
// CORS plugin
|
|
22
|
+
await fastify.register(require('@fastify/cors'), {
|
|
23
|
+
origin: process.env.CORS_ORIGIN || '*',
|
|
24
|
+
credentials: true
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Swagger documentation
|
|
28
|
+
const swaggerConfig = require('@root/config/swagger/admin');
|
|
29
|
+
|
|
30
|
+
await fastify.register(require('@fastify/swagger'), swaggerConfig.swagger);
|
|
31
|
+
|
|
32
|
+
// Swagger UI
|
|
33
|
+
await fastify.register(require('@fastify/swagger-ui'), swaggerConfig.swaggerUi);
|
|
34
|
+
|
|
35
|
+
// Fastify Multipart plugin
|
|
36
|
+
fastify.register(require('@fastify/multipart'), {
|
|
37
|
+
attachFieldsToBody: true, // Optional: attaches fields to the body object
|
|
38
|
+
throwFileSizeLimit: true, // Optional: throws an error if the file size limit is exceeded
|
|
39
|
+
limits: {
|
|
40
|
+
fieldNameSize: 100, // Max field name size in bytes
|
|
41
|
+
fieldSize: 100, // Max field value size in bytes
|
|
42
|
+
fields: 10, // Max number of non-file fields
|
|
43
|
+
fileSize: 1000000, // For multipart forms, the max file size in bytes
|
|
44
|
+
files: 1, // Max number of file fields
|
|
45
|
+
headerPairs: 2000, // Max number of header key=>value pairs
|
|
46
|
+
parts: 1000 // For multipart forms, the max number of parts (fields + files)
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Register other middleware
|
|
51
|
+
fastify.addHook('preHandler', Middleware.languageDetection());
|
|
52
|
+
fastify.addHook('preHandler', Middleware.responseFormatter());
|
|
53
|
+
|
|
54
|
+
// Health check endpoint
|
|
55
|
+
fastify.get('/health', async (request, reply) => {
|
|
56
|
+
return {
|
|
57
|
+
status: 'ok',
|
|
58
|
+
service: 'admin',
|
|
59
|
+
timestamp: new Date().toISOString(),
|
|
60
|
+
uptime: process.uptime(),
|
|
61
|
+
pid: process.pid
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Load admin routes, common routes, and auto routes
|
|
67
|
+
async function loadAdminRoutes() {
|
|
68
|
+
try {
|
|
69
|
+
// Load admin routes
|
|
70
|
+
await loadRoutes(fastify, __dirname + '/../routes/admin', true, 'admin');
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error('Error loading admin routes:', error);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Start admin service
|
|
78
|
+
async function start() {
|
|
79
|
+
try {
|
|
80
|
+
|
|
81
|
+
// Initialize Store
|
|
82
|
+
await Store.init();
|
|
83
|
+
|
|
84
|
+
// Register plugins
|
|
85
|
+
await registerPlugins();
|
|
86
|
+
|
|
87
|
+
// Load admin routes
|
|
88
|
+
await loadAdminRoutes();
|
|
89
|
+
|
|
90
|
+
// Start server
|
|
91
|
+
const port = process.env.ADMIN_PORT || 3001;
|
|
92
|
+
const host = process.env.HOST || '0.0.0.0';
|
|
93
|
+
|
|
94
|
+
fastify.listen({ port, host });
|
|
95
|
+
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error('Error starting admin service:', error);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Graceful shutdown
|
|
103
|
+
process.on('SIGINT', async () => {
|
|
104
|
+
try {
|
|
105
|
+
await fastify.close();
|
|
106
|
+
process.exit(0);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error('Error during admin service shutdown:', error);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
process.on('SIGTERM', async () => {
|
|
114
|
+
try {
|
|
115
|
+
await fastify.close();
|
|
116
|
+
process.exit(0);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error('Error during admin service shutdown:', error);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Start the admin service
|
|
124
|
+
start();
|
package/workers/api.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
require('module-alias/register')
|
|
2
|
+
require('dotenv').config();
|
|
3
|
+
|
|
4
|
+
const fastify = require('fastify')({
|
|
5
|
+
logger: {
|
|
6
|
+
level: process.env.LOG_LEVEL || 'info'
|
|
7
|
+
}
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const { loadRoutes } = require('@root/utils/routeLoader');
|
|
11
|
+
const Middleware = require('@root/utils/middleware');
|
|
12
|
+
|
|
13
|
+
// Register plugins
|
|
14
|
+
async function registerPlugins() {
|
|
15
|
+
// Set error handler first, before any plugins
|
|
16
|
+
fastify.setErrorHandler(Middleware.errorHandler);
|
|
17
|
+
fastify.setNotFoundHandler(Middleware.errorNotFoundHandler);
|
|
18
|
+
|
|
19
|
+
// CORS plugin
|
|
20
|
+
await fastify.register(require('@fastify/cors'), {
|
|
21
|
+
origin: process.env.CORS_ORIGIN || '*',
|
|
22
|
+
credentials: true
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Swagger documentation
|
|
26
|
+
const swaggerConfig = require('@root/config/swagger/api');
|
|
27
|
+
await fastify.register(require('@fastify/swagger'), swaggerConfig.swagger);
|
|
28
|
+
|
|
29
|
+
// Swagger UI
|
|
30
|
+
await fastify.register(require('@fastify/swagger-ui'), swaggerConfig.swaggerUi);
|
|
31
|
+
|
|
32
|
+
// Register other middleware
|
|
33
|
+
fastify.addHook('preHandler', Middleware.languageDetection());
|
|
34
|
+
fastify.addHook('preHandler', Middleware.responseFormatter());
|
|
35
|
+
|
|
36
|
+
// Health check endpoint
|
|
37
|
+
fastify.get('/health', async (request, reply) => {
|
|
38
|
+
return {
|
|
39
|
+
status: 'ok',
|
|
40
|
+
service: 'api',
|
|
41
|
+
timestamp: new Date().toISOString(),
|
|
42
|
+
uptime: process.uptime(),
|
|
43
|
+
pid: process.pid
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Load API routes, common routes, and auto routes
|
|
49
|
+
async function loadApiRoutes() {
|
|
50
|
+
try {
|
|
51
|
+
// Load API routes
|
|
52
|
+
await loadRoutes(fastify, __dirname + '/../routes/api', true, 'api');
|
|
53
|
+
fastify.log.info('API routes loaded successfully');
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error('Error loading API routes:', error);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Start API service
|
|
61
|
+
async function start() {
|
|
62
|
+
try {
|
|
63
|
+
|
|
64
|
+
// Register plugins
|
|
65
|
+
await registerPlugins();
|
|
66
|
+
|
|
67
|
+
// Load API routes
|
|
68
|
+
await loadApiRoutes();
|
|
69
|
+
|
|
70
|
+
// Start server
|
|
71
|
+
const port = process.env.API_PORT || 3000;
|
|
72
|
+
const host = process.env.HOST || '0.0.0.0';
|
|
73
|
+
|
|
74
|
+
fastify.listen({ port, host });
|
|
75
|
+
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error(error);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Graceful shutdown
|
|
83
|
+
process.on('SIGINT', async () => {
|
|
84
|
+
fastify.log.info('API service received SIGINT, shutting down gracefully...');
|
|
85
|
+
try {
|
|
86
|
+
await fastify.close();
|
|
87
|
+
process.exit(0);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error('Error during API service shutdown:', error);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
process.on('SIGTERM', async () => {
|
|
95
|
+
fastify.log.info('API service received SIGTERM, shutting down gracefully...');
|
|
96
|
+
try {
|
|
97
|
+
await fastify.close();
|
|
98
|
+
process.exit(0);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error('Error during API service shutdown:', error);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Start the API service
|
|
106
|
+
start();
|