@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,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 };
@@ -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();