@ecopex/ecopex-framework 1.0.9 → 1.0.12

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/config/swagger.js CHANGED
@@ -2,12 +2,13 @@
2
2
  * Swagger configuration for Admin service
3
3
  */
4
4
  const { config } = require('../stores/config');
5
+
5
6
  module.exports = {
6
7
  swagger: {
7
8
  openapi: {
8
9
  info: {
9
- title: config.name + ' API',
10
- description: config.name + ' API documentation for ' + config.name,
10
+ title: (config.name || '').toUpperCase() + ' API',
11
+ description: (config.name || '').toUpperCase() + ' API DOCUMENTATION',
11
12
  version: '1.0.0'
12
13
  },
13
14
  host: `${config.host || 'localhost'}:${config.port || 3001}`,
@@ -40,6 +41,6 @@ module.exports = {
40
41
  }
41
42
  },
42
43
  swaggerUi: {
43
- routePrefix: '/docs'
44
+ routePrefix: '/'
44
45
  }
45
46
  };
package/index.js CHANGED
@@ -15,7 +15,7 @@ const start = async (config) => {
15
15
 
16
16
  module.exports = {
17
17
  start,
18
- db: knex.instance,
18
+ knex: knex.connection,
19
19
  date: date,
20
20
  jwt: jwt,
21
21
  bcrypt: bcrypt,
@@ -98,6 +98,13 @@ const ipcheck = (req, whitelists = '') => {
98
98
  }
99
99
  }
100
100
 
101
+ const toTitleCase = (str = '') => {
102
+ return str.replace(
103
+ /\w\S*/g,
104
+ text => text.charAt(0).toUpperCase() + text.substring(1).toLowerCase()
105
+ );
106
+ }
107
+
101
108
  module.exports = {
102
109
  emailValidation,
103
110
  phoneValidation,
@@ -109,5 +116,6 @@ module.exports = {
109
116
  checkPassoword,
110
117
  ipcheck,
111
118
  sleep,
112
- roundTo
119
+ roundTo,
120
+ toTitleCase
113
121
  }
@@ -1,6 +1,6 @@
1
1
  const { loadRoutes } = require('../utils/routeLoader');
2
2
  const Middleware = require('../utils/middleware');
3
- const { setConfig } = require('../stores/config');
3
+ const { setConfig, config: configStore } = require('../stores/config');
4
4
 
5
5
  let fastifyInstance = null;
6
6
 
@@ -15,8 +15,8 @@ async function registerPlugins(config) {
15
15
  });
16
16
 
17
17
  // Set error handler first, before any plugins
18
- fastifyInstance.setErrorHandler(Middleware.errorHandler);
19
- fastifyInstance.setNotFoundHandler(Middleware.errorNotFoundHandler);
18
+ fastifyInstance.setErrorHandler((error, request, reply) => Middleware.errorHandler(error, request, reply, config));
19
+ fastifyInstance.setNotFoundHandler((request, reply) => Middleware.errorNotFoundHandler(request, reply));
20
20
 
21
21
  // CORS plugin
22
22
  await fastifyInstance.register(require('@fastify/cors'), {
@@ -52,14 +52,23 @@ async function registerPlugins(config) {
52
52
  fastifyInstance.addHook('preHandler', Middleware.responseFormatter());
53
53
 
54
54
  // Health check endpoint
55
- fastifyInstance.get('/health', async (request, reply) => {
56
- return {
57
- status: 'ok',
58
- service: config.name,
59
- timestamp: new Date().toISOString(),
60
- uptime: process.uptime(),
61
- pid: process.pid
62
- };
55
+ fastifyInstance.route({
56
+ method: 'GET',
57
+ url: '/' + (configStore.prefix ? configStore.prefix + '/' : '') + 'health',
58
+ schema: {
59
+ description: 'Health check',
60
+ summary: 'Health check',
61
+ tags: ['Global Services']
62
+ },
63
+ handler: async (request, reply) => {
64
+ return {
65
+ status: 'ok',
66
+ service: config.name,
67
+ timestamp: new Date().toISOString(),
68
+ uptime: process.uptime(),
69
+ pid: process.pid
70
+ };
71
+ }
63
72
  });
64
73
 
65
74
  return fastifyInstance;
@@ -70,7 +79,7 @@ async function loadAdminRoutes(config) {
70
79
  try {
71
80
  // Load admin routes
72
81
  if(fastifyInstance) {
73
- await loadRoutes(fastifyInstance, './routes/' + config.name, true, config.name);
82
+ await loadRoutes(fastifyInstance, './routes/' + config.name, config.common || false, config.name);
74
83
  }
75
84
  } catch (error) {
76
85
  console.error('Error loading admin routes:', error);
@@ -83,7 +92,7 @@ async function start(config) {
83
92
  try {
84
93
 
85
94
  setConfig(config);
86
-
95
+
87
96
  // Register plugins
88
97
  await registerPlugins(config);
89
98
 
package/libraries/knex.js CHANGED
@@ -1,9 +1,10 @@
1
1
  const knex = require('knex');
2
2
 
3
- let instance = null;
3
+ const connection = {
4
+ db: null
5
+ }
4
6
 
5
- const initialize = async (config) => {
6
- const { config: configStore } = require('../stores/config');
7
+ const initialize = async (configStore) => {
7
8
  const databaseConfig = configStore?.database || {};
8
9
 
9
10
  const db_config = {
@@ -30,10 +31,12 @@ const initialize = async (config) => {
30
31
  }
31
32
 
32
33
  const db = knex(db_config[configStore.development ? 'development' : 'production']);
33
- instance = db;
34
+ connection.db = db;
35
+
36
+ return connection.db;
34
37
  }
35
38
 
36
39
  module.exports = {
37
40
  initialize,
38
- instance
41
+ connection
39
42
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopex/ecopex-framework",
3
- "version": "1.0.9",
3
+ "version": "1.0.12",
4
4
  "description": "Javascript Framework for API and Admin Panel",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -1,4 +1,4 @@
1
- const db = require('../../libraries/knex');
1
+ const knex = require('../../libraries/knex');
2
2
  const BCRYPT = require('../../libraries/bcrypt');
3
3
  const bcrypt = new BCRYPT();
4
4
  const i18n = require('../../utils/i18n');
@@ -26,11 +26,12 @@ async function getAll(request, reply, routeOptions = {}) {
26
26
  const routeConfig = routeOptions.routeConfig || {};
27
27
  const primaryKey = routeOptions.primaryKey || 'id';
28
28
  const filters = routeOptions.filters || {};
29
+ const joinTables = routeOptions.joinTables || {};
29
30
  const splitOrder = order.split(':');
30
31
  const orderBy = `${tableName}.${(splitOrder[0] || primaryKey)}`;
31
32
  const orderDirection = splitOrder[1] || 'desc';
32
33
 
33
- let query = db.instance(tableName + ' as ' + tableName).select(`${tableName}.*`);
34
+ let query = knex.connection.db(tableName + ' as ' + tableName).select(`${tableName}.*`);
34
35
 
35
36
  // Apply search filter if provided
36
37
  if (search && routeConfig.searchable_fields) {
@@ -49,18 +50,18 @@ async function getAll(request, reply, routeOptions = {}) {
49
50
  query.where(routeConfig.owned, request.user?.id || 0);
50
51
  query.where(routeConfig.owned_type, request.user?.type || 'user');
51
52
  }
52
-
53
- if(routeConfig.join_tables) {
54
- for(let i = 0; i < Object.values(routeConfig.join_tables).length; i++) {
55
- const tableKey = Object.keys(routeConfig.join_tables)[i];
56
- let joinTable = Object.values(routeConfig.join_tables)[i];
53
+
54
+ if(joinTables) {
55
+ for(let i = 0; i < Object.values(joinTables).length; i++) {
56
+ const tableKey = Object.keys(joinTables)[i];
57
+ let joinTable = Object.values(joinTables)[i];
57
58
  if(joinTable.type == 'one-to-one') {
58
59
  if(joinTable.extra_where) {
59
60
  query.leftJoin(joinTable.table + ' as ' + tableKey, `${tableName}.${joinTable.foreign_key} = ${tableKey}.${joinTable.primary_key} AND ${joinTable.extra_where}`);
60
61
  } else {
61
62
  query.leftJoin(joinTable.table + ' as ' + tableKey, `${tableName}.${joinTable.foreign_key}`, `${tableKey}.${joinTable.primary_key}`);
62
63
  }
63
- query.select(`${tableKey}.*`);
64
+ query.select(knex.connection.db.raw(Object.entries(joinTable.properties || {}).map(n => `${tableKey}.${n[0]} as ${n[0]}`).join(', ')));
64
65
  }
65
66
  }
66
67
 
@@ -107,14 +108,11 @@ async function getAll(request, reply, routeOptions = {}) {
107
108
 
108
109
  const total = parseInt(count);
109
110
 
110
- const rows = structureRows(items, tableName, routeConfig.join_tables)
111
+ const rows = structureRows(items, tableName, joinTables)
111
112
 
112
113
  // Process join tables if configured
113
- const processedItems = await processJoinTables(rows, routeConfig.join_tables);
114
+ const processedItems = await processJoinTables(rows, joinTables);
114
115
 
115
- // console.log(processedItems);
116
-
117
-
118
116
  const totalPages = Math.ceil(total / limit);
119
117
 
120
118
  return reply.send({
@@ -143,33 +141,30 @@ async function getAll(request, reply, routeOptions = {}) {
143
141
  * @param {Object} routeOptions - The route options
144
142
  * @returns {Promise<Object>} - The response object
145
143
  */
146
- async function getById(request, reply, routeOptions = {}) {
144
+ async function getById(request, reply, routeOptions = {}, inner = false) {
147
145
 
148
146
  if(routeOptions.store) {
149
147
  return get_by_primary_key(routeOptions.store, request.params[routeOptions.primaryKey], reply, routeOptions);
150
148
  }
151
149
 
152
150
  try {
153
- const primaryKey = routeOptions.primaryKey || 'id';
154
- const { [primaryKey]: id } = request.params;
155
151
  const tableName = routeOptions.tableName || 'users';
156
- const routeConfig = routeOptions.routeConfig || {};
157
-
158
- const item_query = db.instance(tableName)
152
+ const joinTables = routeOptions.joinTables || {};
153
+ const item_query = knex.connection.db(tableName)
159
154
  .select(`${tableName}.*`)
160
- .where(primaryKey, id);
161
-
162
- if(routeConfig.join_tables) {
163
- for(let i = 0; i < Object.values(routeConfig.join_tables).length; i++) {
164
- const tableKey = Object.keys(routeConfig.join_tables)[i];
165
- let joinTable = Object.values(routeConfig.join_tables)[i];
155
+ .where(request.params);
156
+
157
+ if(joinTables) {
158
+ for(let i = 0; i < Object.values(joinTables).length; i++) {
159
+ const tableKey = Object.keys(joinTables)[i];
160
+ let joinTable = Object.values(joinTables)[i];
166
161
  if(joinTable.type == 'one-to-one') {
167
162
  if(joinTable.extra_where) {
168
163
  item_query.leftJoin(joinTable.table + ' as ' + tableKey, `${tableName}.${joinTable.foreign_key} = ${tableKey}.${joinTable.primary_key} AND ${joinTable.extra_where}`);
169
164
  } else {
170
165
  item_query.leftJoin(joinTable.table + ' as ' + tableKey, `${tableName}.${joinTable.foreign_key}`, `${tableKey}.${joinTable.primary_key}`);
171
166
  }
172
- item_query.select(`${tableKey}.*`);
167
+ item_query.select(knex.connection.db.raw(Object.entries(joinTable.properties || {}).map(n => `${tableKey}.${n[0]} as ${n[0]}`).join(', ')));
173
168
  }
174
169
  }
175
170
 
@@ -177,16 +172,15 @@ async function getById(request, reply, routeOptions = {}) {
177
172
  }
178
173
 
179
174
  const item_data = await item_query.first();
180
- console.log(item_data);
181
175
 
182
176
  let item = {}
183
177
 
184
178
 
185
- if(routeConfig.join_tables) {
179
+ if(joinTables) {
186
180
  item = {
187
181
  ...item_data[tableName],
188
182
  }
189
- const join_one_to_one = Object.entries(routeConfig.join_tables).filter(n => n[1].type === 'one-to-one');
183
+ const join_one_to_one = Object.entries(joinTables).filter(n => n[1].type === 'one-to-one');
190
184
  if(join_one_to_one.length > 0) {
191
185
  for(let i = 0; i < join_one_to_one.length; i++) {
192
186
  const join_table = join_one_to_one[i];
@@ -198,26 +192,44 @@ async function getById(request, reply, routeOptions = {}) {
198
192
  }
199
193
 
200
194
  if (!item) {
201
- return reply.status(404).send({
202
- status: false,
203
- message: request.t('messages.record_not_found')
204
- });
195
+ if(!inner) {
196
+ return reply.status(404).send({
197
+ status: false,
198
+ message: request.t('messages.record_not_found')
199
+ });
200
+ } else {
201
+ return {
202
+ status: false,
203
+ message: request.t('messages.record_not_found')
204
+ };
205
+ }
205
206
  }
206
207
 
207
208
  // Process join tables if configured
208
- const processedItem = await processJoinTables([item], routeConfig.join_tables);
209
+ const processedItem = await processJoinTables([item], joinTables);
209
210
  const result = processedItem[0];
210
211
 
211
- return reply.send({
212
- status: true,
213
- data: result
214
- });
212
+ if(!inner) {
213
+ return reply.send({
214
+ status: true,
215
+ data: result
216
+ });
217
+ } else {
218
+ return result;
219
+ }
215
220
  } catch (error) {
216
221
  console.error('Error getting record by ID:', error);
217
- return reply.status(500).send({
218
- status: false,
219
- message: request.t('messages.internal_server_error')
220
- });
222
+ if(!inner) {
223
+ return reply.status(500).send({
224
+ status: false,
225
+ message: request.t('messages.internal_server_error')
226
+ });
227
+ } else {
228
+ return {
229
+ status: false,
230
+ message: request.t('messages.internal_server_error')
231
+ };
232
+ }
221
233
  }
222
234
  }
223
235
 
@@ -237,7 +249,6 @@ async function create(request, reply, routeOptions = {}) {
237
249
  try {
238
250
  const tableName = routeOptions.tableName || 'users';
239
251
  const primaryKey = routeOptions.primaryKey || 'id';
240
- const routeConfig = routeOptions.routeConfig || {};
241
252
  const schema = routeOptions.schema || {};
242
253
 
243
254
  // Use payload field if available, otherwise use request.body directly
@@ -259,18 +270,14 @@ async function create(request, reply, routeOptions = {}) {
259
270
  }
260
271
  }
261
272
  }
262
-
273
+
263
274
  try {
264
- const [id] = await db.instance(tableName)
275
+ const [id] = await knex.connection.db(tableName)
265
276
  .insert({
266
- ...data,
267
- created_at: new Date(),
268
- updated_at: new Date()
277
+ ...data
269
278
  });
270
279
 
271
- const item = await db.instance(tableName)
272
- .where(primaryKey, id)
273
- .first();
280
+ const item = await getById({ ...request, params: { [primaryKey]: id } }, {}, { ...routeOptions, primaryKey: primaryKey }, true)
274
281
 
275
282
  return reply.send({
276
283
  status: true,
@@ -314,8 +321,8 @@ async function update(request, reply, routeOptions = {}) {
314
321
  try {
315
322
  const tableName = routeOptions.tableName || 'users';
316
323
  const primaryKey = routeOptions.primaryKey || 'id';
317
- const routeConfig = routeOptions.routeConfig || {};
318
324
  const schema = routeOptions.schema || {};
325
+ const routeConfig = routeOptions.routeConfig || {};
319
326
 
320
327
  // Use payload field if available, otherwise use request.body directly
321
328
  let data = request.body;
@@ -345,7 +352,7 @@ async function update(request, reply, routeOptions = {}) {
345
352
 
346
353
 
347
354
  // Check if record exists
348
- const existingItem = await db.instance(tableName)
355
+ const existingItem = await knex.connection.db(tableName)
349
356
  .where(request.params)
350
357
  .first();
351
358
 
@@ -363,16 +370,14 @@ async function update(request, reply, routeOptions = {}) {
363
370
  }
364
371
  }
365
372
 
366
- await db.instance(tableName)
373
+ await knex.connection.db(tableName)
367
374
  .where(request.params)
368
375
  .update({
369
376
  ...data,
370
377
  updated_at: new Date()
371
378
  })
372
379
 
373
- const updatedItem = await db.instance(tableName)
374
- .where(request.params)
375
- .first();
380
+ const updatedItem = await getById({ ...request }, {}, { ...routeOptions, primaryKey: primaryKey }, true);
376
381
 
377
382
  return reply.send({
378
383
  status: true,
@@ -403,11 +408,10 @@ async function upload(request, reply, routeOptions = {}) {
403
408
  try {
404
409
  const tableName = routeOptions.tableName || 'users';
405
410
  const primaryKey = routeOptions.primaryKey || 'id';
406
- const { [primaryKey]: id } = request.params;
407
411
  const routeConfig = routeOptions.routeConfig || {};
408
412
 
409
- const existingItem = await db.instance(tableName)
410
- .where(primaryKey, id)
413
+ const existingItem = await knex.connection.db(tableName)
414
+ .where(request.params)
411
415
  .first();
412
416
 
413
417
  if (!existingItem) {
@@ -417,7 +421,7 @@ async function upload(request, reply, routeOptions = {}) {
417
421
  });
418
422
  }
419
423
 
420
- const uploadResult = await uploadFile(request.body[routeConfig.upload_field], id, tableName);
424
+ const uploadResult = await uploadFile(request.body[routeConfig.upload_field], request.params[primaryKey], tableName);
421
425
  if(!uploadResult.status) {
422
426
  return reply.status(500).send({
423
427
  status: false,
@@ -425,8 +429,8 @@ async function upload(request, reply, routeOptions = {}) {
425
429
  });
426
430
  }
427
431
 
428
- await db.instance(tableName)
429
- .where(primaryKey, id)
432
+ await knex.connection.db(tableName)
433
+ .where(request.params)
430
434
  .update({
431
435
  [routeConfig.upload_field]: uploadResult.file.fileUrl
432
436
  });
@@ -464,7 +468,7 @@ async function deleteRecord(request, reply, routeOptions = {}) {
464
468
  const tableName = routeOptions.tableName || 'users';
465
469
 
466
470
  // Check if record exists
467
- const existingItem = await db.instance(tableName)
471
+ const existingItem = await knex.connection.db(tableName)
468
472
  .where('id', id)
469
473
  .first();
470
474
 
@@ -475,7 +479,7 @@ async function deleteRecord(request, reply, routeOptions = {}) {
475
479
  });
476
480
  }
477
481
 
478
- await db.instance(tableName)
482
+ await knex.connection.db(tableName)
479
483
  .where('id', id)
480
484
  .del();
481
485
 
@@ -524,7 +528,7 @@ const generateRandomString = async (from_string = null, field_name = null, table
524
528
  * @returns {Promise<boolean>} - The unique status
525
529
  */
526
530
  const checkUnique = async (value = null, field_name = null, tableName = null) => {
527
- const existing_item = await db.instance(tableName).select(field_name).where(field_name, value).first();
531
+ const existing_item = await knex.connection.db(tableName).select(field_name).where(field_name, value).first();
528
532
  return existing_item ? true : false;
529
533
  }
530
534
 
@@ -594,7 +598,7 @@ const processJoinTables = async (items, joinTables) => {
594
598
 
595
599
  const personal_ids = items.map(item => item[joinTable.primary_key]);
596
600
 
597
- const personal_items = db.instance(joinTable.table).whereIn(joinTable.table + '.' + joinTable.foreign_key, personal_ids);
601
+ const personal_items = knex.connection.db(joinTable.table).whereIn(joinTable.table + '.' + joinTable.foreign_key, personal_ids);
598
602
 
599
603
  if(joinTable.extra_where) {
600
604
  personal_items.where(db.raw(joinTable.table + '.' + joinTable.extra_where));
package/stores/base.js CHANGED
@@ -1,9 +1,9 @@
1
- const knex = require('../libraries/knex');
1
+ const { connection } = require('../libraries/knex');
2
2
 
3
3
  module.exports = class StoreModel {
4
4
  constructor(name = 'store_model', primary_key = 'id', schema = {}) {
5
5
  this.name = name;
6
- this.knex = knex;
6
+ this.knex = connection.db;
7
7
  this.data = new Map();
8
8
  this.primary_key = primary_key;
9
9
  this.schema = schema;
package/stores/config.js CHANGED
@@ -1,7 +1,12 @@
1
- let config = {};
1
+ const config = {};
2
2
 
3
- const setConfig = (config) => {
4
- config = config;
3
+ const setConfig = (newConfig) => {
4
+ if(Object.keys(newConfig).length > 0) {
5
+ for(const key in newConfig) {
6
+ config[key] = newConfig[key];
7
+ }
8
+ }
9
+ return config;
5
10
  }
6
11
 
7
12
  module.exports = {
@@ -3,6 +3,7 @@ const path = require('path');
3
3
  const handlers = require('../routes/auto/handler');
4
4
  const general = require('../libraries/general');
5
5
  const { add_model } = require('../stores');
6
+ const { config: configStore } = require('../stores/config');
6
7
 
7
8
  /**
8
9
  * JSON Route Loader
@@ -19,7 +20,7 @@ class JsonRouteLoader {
19
20
  * Load all JSON route configurations from routes/admin folder
20
21
  * @param {Object} fastify - Fastify instance
21
22
  */
22
- async loadJsonRoutes(fastify, routesDir, workerName) {
23
+ async loadJsonRoutes(fastify, routesDir, workerName, type = 'service') {
23
24
  const adminPath = path.resolve(routesDir);
24
25
 
25
26
  if(!workerName) {
@@ -39,7 +40,7 @@ class JsonRouteLoader {
39
40
 
40
41
  for (const jsonFile of jsonFiles) {
41
42
  try {
42
- await this.loadJsonRoute(fastify, path.join(adminPath, jsonFile), workerName);
43
+ await this.loadJsonRoute(fastify, path.join(adminPath, jsonFile), workerName, type);
43
44
  } catch (error) {
44
45
  console.log(error.message);
45
46
  }
@@ -51,7 +52,7 @@ class JsonRouteLoader {
51
52
  * @param {Object} fastify - Fastify instance
52
53
  * @param {string} jsonPath - Path to JSON file
53
54
  */
54
- async loadJsonRoute(fastify, jsonPath, workerName) {
55
+ async loadJsonRoute(fastify, jsonPath, workerName, type = 'service') {
55
56
  try {
56
57
 
57
58
  const config = require(jsonPath);
@@ -66,6 +67,7 @@ class JsonRouteLoader {
66
67
  const store = config.store || null;
67
68
  const routes = config.routes || {};
68
69
  const schema = config.schema || {};
70
+ const joinTables = config.join_tables || {};
69
71
 
70
72
  // Validate configuration
71
73
  this.validateConfig(config);
@@ -75,7 +77,7 @@ class JsonRouteLoader {
75
77
  const routeName = route.action;
76
78
  const routeConfig = route;
77
79
  if (this.supportedRoutes.includes(routeName)) {
78
- await this.registerRoute(fastify, tableName, routeName, routeConfig, primaryKey, workerName, fileName, store, schema);
80
+ await this.registerRoute(fastify, tableName, routeName, routeConfig, primaryKey, workerName, fileName, store, schema, joinTables, type);
79
81
  if(store) {
80
82
  await add_model(store, primaryKey, schema);
81
83
  }
@@ -111,9 +113,9 @@ class JsonRouteLoader {
111
113
  * @param {string} routeName - Route name (list, get, create, update, delete)
112
114
  * @param {Object} routeConfig - Route configuration
113
115
  */
114
- async registerRoute(fastify, tableName, routeName, routeConfig, primaryKey, workerName, fileName, store, schema) {
116
+ async registerRoute(fastify, tableName, routeName, routeConfig, primaryKey, workerName, fileName, store, schema, joinTables, type = 'service') {
115
117
 
116
- const routeDefinition = this.generateRouteDefinition(tableName, routeName, routeConfig, primaryKey, fileName, schema);
118
+ const routeDefinition = this.generateRouteDefinition(tableName, routeName, routeConfig, primaryKey, fileName, schema, type, joinTables);
117
119
  const routeKey = `${routeDefinition.method}:${routeDefinition.url}`;
118
120
 
119
121
  if (this.registeredRoutes.has(routeKey)) {
@@ -148,7 +150,7 @@ class JsonRouteLoader {
148
150
  },
149
151
  handler: async (request, reply) => {
150
152
  // Pass table name and route config to handler as additional parameters
151
- return handler(request, reply, { tableName, routeConfig, schema, primaryKey, store, filters });
153
+ return handler(request, reply, { tableName, routeConfig, schema, primaryKey, store, filters, joinTables });
152
154
  }
153
155
  });
154
156
 
@@ -166,40 +168,41 @@ class JsonRouteLoader {
166
168
  * @param {Object} routeConfig - Route configuration
167
169
  * @returns {Object} Route definition
168
170
  */
169
- generateRouteDefinition(tableName, routeName, routeConfig, primaryKey, fileName, schema) {
170
- const baseUrl = `/${fileName}`;
171
+ generateRouteDefinition(tableName, routeName, routeConfig, primaryKey, fileName, schema, type = 'service', joinTables = {}) {
172
+ const prefix = configStore.prefix ? configStore.prefix + '/' : '';
173
+ const baseUrl = `/${prefix}${fileName}`;
171
174
  primaryKey = primaryKey || 'id';
172
175
 
173
176
  const routeDefinitions = {
174
177
  list: {
175
178
  method: 'GET',
176
179
  url: baseUrl,
177
- schema: this.generateListSchema(tableName, routeConfig, primaryKey, schema)
180
+ schema: this.generateListSchema(tableName, routeConfig, primaryKey, schema, type, joinTables)
178
181
  },
179
182
  get: {
180
183
  method: 'GET',
181
184
  url: `${baseUrl}/:${primaryKey}`,
182
- schema: this.generateGetSchema(tableName, routeConfig, primaryKey, schema)
185
+ schema: this.generateGetSchema(tableName, routeConfig, primaryKey, schema, type, joinTables)
183
186
  },
184
187
  create: {
185
188
  method: 'POST',
186
189
  url: baseUrl,
187
- schema: this.generateCreateSchema(tableName, routeConfig, primaryKey, schema)
190
+ schema: this.generateCreateSchema(tableName, routeConfig, primaryKey, schema, type, joinTables)
188
191
  },
189
192
  update: {
190
193
  method: 'PUT',
191
194
  url: `${baseUrl}/:${primaryKey}`,
192
- schema: this.generateUpdateSchema(tableName, routeConfig, primaryKey, schema)
195
+ schema: this.generateUpdateSchema(tableName, routeConfig, primaryKey, schema, type, joinTables)
193
196
  },
194
197
  delete: {
195
198
  method: 'DELETE',
196
199
  url: `${baseUrl}/:${primaryKey}`,
197
- schema: this.generateDeleteSchema(tableName, routeConfig, primaryKey, schema)
200
+ schema: this.generateDeleteSchema(tableName, routeConfig, primaryKey, schema, type, joinTables)
198
201
  },
199
202
  upload: {
200
203
  method: 'POST',
201
- url: `${baseUrl}/:${primaryKey}/upload`,
202
- schema: this.generateUploadSchema(tableName, routeConfig, primaryKey, schema),
204
+ url: `${baseUrl}/:${primaryKey}/${routeConfig.upload_field || 'upload'}`,
205
+ schema: this.generateUploadSchema(tableName, routeConfig, primaryKey, schema, type, joinTables),
203
206
  validatorCompiler: ({ schema }) => {
204
207
  return (data) => {
205
208
  return true;
@@ -220,12 +223,12 @@ class JsonRouteLoader {
220
223
  * @param {Object} filters - Filters object
221
224
  * @returns {Object} Schema
222
225
  */
223
- generateListSchema(tableName, routeConfig, primaryKey, schema) {
224
- const itemSchema = this.generateItemSchema(schema, primaryKey);
226
+ generateListSchema(tableName, routeConfig, primaryKey, schema, type = 'service', joinTables = {}) {
227
+ const itemSchema = this.generateItemSchema(schema, primaryKey, joinTables);
225
228
 
226
229
  return {
227
230
  description: `Get all ${tableName} with pagination and search`,
228
- tags: [general.titleCase(tableName)],
231
+ tags: [(type == 'service' ? general.titleCase(configStore.name) + ' | ' : 'Common | ') + 'Specs | ' + general.titleCase(tableName)],
229
232
  summary: routeConfig.summary || `List of ${tableName} with pagination and search`,
230
233
  querystring: {
231
234
  type: 'object',
@@ -279,13 +282,13 @@ class JsonRouteLoader {
279
282
  * @param {Object} routeConfig - Route configuration
280
283
  * @returns {Object} Schema
281
284
  */
282
- generateGetSchema(tableName, routeConfig, primaryKey, schema) {
283
- const itemSchema = this.generateItemSchema(schema, primaryKey);
285
+ generateGetSchema(tableName, routeConfig, primaryKey, schema, type = 'service', joinTables = {}) {
286
+ const itemSchema = this.generateItemSchema(schema, primaryKey, joinTables);
284
287
  primaryKey = primaryKey || 'id';
285
288
 
286
289
  return {
287
290
  description: `Get ${tableName} by ${primaryKey}`,
288
- tags: [general.titleCase(tableName)],
291
+ tags: [(type == 'service' ? general.titleCase(configStore.name) + ' | ' : 'Common | ') + 'Specs | ' + general.titleCase(tableName)],
289
292
  summary: routeConfig.summary || `Get ${tableName} by ${primaryKey}`,
290
293
  params: {
291
294
  type: 'object',
@@ -320,12 +323,12 @@ class JsonRouteLoader {
320
323
  * @param {Object} routeConfig - Route configuration
321
324
  * @returns {Object} Schema
322
325
  */
323
- generateCreateSchema(tableName, routeConfig, primaryKey, schema) {
326
+ generateCreateSchema(tableName, routeConfig, primaryKey, schema, type = 'service', joinTables = {}) {
324
327
  const bodySchema = this.generateBodySchema(schema, false, primaryKey);
325
328
 
326
329
  return {
327
330
  description: `Create new ${tableName}`,
328
- tags: [general.titleCase(tableName)],
331
+ tags: [(type == 'service' ? general.titleCase(configStore.name) + ' | ' : 'Common | ') + 'Specs | ' + general.titleCase(tableName)],
329
332
  summary: routeConfig.summary || `Create new ${tableName}`,
330
333
  body: bodySchema,
331
334
  response: {
@@ -333,7 +336,7 @@ class JsonRouteLoader {
333
336
  type: 'object',
334
337
  properties: {
335
338
  status: { type: 'boolean', default: true },
336
- data: this.generateItemSchema(schema, primaryKey)
339
+ data: this.generateItemSchema(schema, primaryKey, joinTables)
337
340
  },
338
341
  additionalProperties: false
339
342
  },
@@ -354,12 +357,12 @@ class JsonRouteLoader {
354
357
  * @param {Object} routeConfig - Route configuration
355
358
  * @returns {Object} Schema
356
359
  */
357
- generateUpdateSchema(tableName, routeConfig, primaryKey, schema) {
360
+ generateUpdateSchema(tableName, routeConfig, primaryKey, schema, type = 'service', joinTables = {}) {
358
361
  const bodySchema = this.generateBodySchema(schema, true, primaryKey, 'updatable');
359
362
 
360
363
  return {
361
364
  description: `Update ${tableName} by ID`,
362
- tags: [general.titleCase(tableName)],
365
+ tags: [(type == 'service' ? general.titleCase(configStore.name) + ' | ' : 'Common | ') + 'Specs | ' + general.titleCase(tableName)],
363
366
  summary: routeConfig.summary || `Update ${tableName} by ${primaryKey}`,
364
367
  params: {
365
368
  type: 'object',
@@ -375,7 +378,7 @@ class JsonRouteLoader {
375
378
  type: 'object',
376
379
  properties: {
377
380
  status: { type: 'boolean', default: true },
378
- data: this.generateItemSchema(schema, primaryKey)
381
+ data: this.generateItemSchema(schema, primaryKey, joinTables)
379
382
  },
380
383
  additionalProperties: false
381
384
  },
@@ -396,10 +399,10 @@ class JsonRouteLoader {
396
399
  * @param {Object} routeConfig - Route configuration
397
400
  * @returns {Object} Schema
398
401
  */
399
- generateDeleteSchema(tableName, routeConfig, primaryKey, schema) {
402
+ generateDeleteSchema(tableName, routeConfig, primaryKey, schema, type = 'service', joinTables = {}) {
400
403
  return {
401
404
  description: `Delete ${tableName} by ID`,
402
- tags: [general.titleCase(tableName)],
405
+ tags: [(type == 'service' ? general.titleCase(configStore.name) + ' | ' : 'Common | ') + 'Specs | ' + general.titleCase(tableName)],
403
406
  summary: routeConfig.summary || `Delete ${tableName} by ${primaryKey}`,
404
407
  params: {
405
408
  type: 'object',
@@ -414,7 +417,7 @@ class JsonRouteLoader {
414
417
  type: 'object',
415
418
  properties: {
416
419
  status: { type: 'boolean', default: true },
417
- data: this.generateItemSchema(schema, primaryKey)
420
+ data: this.generateItemSchema(schema, primaryKey, joinTables)
418
421
  },
419
422
  additionalProperties: false
420
423
  },
@@ -435,11 +438,11 @@ class JsonRouteLoader {
435
438
  * @param {Object} routeConfig - Route configuration
436
439
  * @returns {Object} Schema
437
440
  */
438
- generateUploadSchema(tableName, routeConfig, primaryKey, schema) {
441
+ generateUploadSchema(tableName, routeConfig, primaryKey, schema, type = 'service', joinTables = {}) {
439
442
  const uploadField = routeConfig.upload_field || 'file';
440
443
  return {
441
444
  description: `Upload file for ${tableName} by ${primaryKey}`,
442
- tags: [general.titleCase(tableName)],
445
+ tags: [(type == 'service' ? general.titleCase(configStore.name) + ' | ' : 'Common | ') + 'Specs | ' + general.titleCase(tableName)],
443
446
  summary: routeConfig.summary || `Upload file for ${tableName} by ${primaryKey}`,
444
447
  params: {
445
448
  type: 'object',
@@ -462,7 +465,7 @@ class JsonRouteLoader {
462
465
  type: 'object',
463
466
  properties: {
464
467
  status: { type: 'boolean', default: true },
465
- data: this.generateItemSchema(schema, primaryKey)
468
+ data: this.generateItemSchema(schema, primaryKey, joinTables)
466
469
  },
467
470
  additionalProperties: false
468
471
  },
@@ -482,7 +485,7 @@ class JsonRouteLoader {
482
485
  * @param {Object} itemSchema - Item schema from config
483
486
  * @returns {Object} Item schema
484
487
  */
485
- generateItemSchema(schema, primaryKey) {
488
+ generateItemSchema(schema, primaryKey, joinTables = {}) {
486
489
  if (!schema) {
487
490
  return { type: 'object' };
488
491
  }
@@ -507,6 +510,18 @@ class JsonRouteLoader {
507
510
  }
508
511
  }
509
512
 
513
+ if(Object.entries(joinTables).length > 0) {
514
+ for(const [key, value] of Object.entries(joinTables)) {
515
+ if(value.type == 'one-to-one') {
516
+ withoutReferences[value.table] = {
517
+ type: 'object',
518
+ properties: value.properties,
519
+ additionalProperties: false
520
+ };
521
+ }
522
+ }
523
+ }
524
+
510
525
  return {
511
526
  type: 'object',
512
527
  properties: withoutReferences,
@@ -1,5 +1,4 @@
1
1
  const i18n = require('./i18n');
2
- const db = require('../libraries/knex');
3
2
  const { config } = require('../stores/config');
4
3
  /**
5
4
  * Middleware utilities for Fastify
@@ -17,10 +16,6 @@ class Middleware {
17
16
  // Add locale to request object
18
17
  request.locale = locale;
19
18
  request.t = (key, params = {}) => i18n.t(key, locale, params);
20
-
21
- const language = await db.instance('languages').where('code', locale).first();
22
-
23
- request.language = language ? language.language_id : 1;
24
19
 
25
20
  // Add locale to reply headers
26
21
  reply.header('Content-Language', locale);
@@ -30,8 +25,8 @@ class Middleware {
30
25
  /**
31
26
  * Error handler middleware with custom response format
32
27
  */
33
- static errorHandler(error, request, reply) {
34
-
28
+ static errorHandler(error, request, reply, config) {
29
+
35
30
  if(config.development) {
36
31
  console.log(error);
37
32
  }
@@ -68,7 +63,7 @@ class Middleware {
68
63
  code: 404
69
64
  });
70
65
  }
71
-
66
+
72
67
  // Handle other errors
73
68
  const statusCode = error.statusCode || 500;
74
69
 
@@ -116,9 +111,11 @@ class Middleware {
116
111
  */
117
112
  static responseFormatter() {
118
113
  return async (request, reply) => {
114
+
115
+ const route_url = request?.routeOptions?.url || request.url;
119
116
 
120
117
  // Skip response formatting for Swagger routes
121
- if (request.routeOptions.url.startsWith('/docs') || request.routeOptions.url.startsWith('/docs/')) {
118
+ if (route_url.startsWith('/') || route_url.startsWith('/')) {
122
119
  return;
123
120
  }
124
121
 
@@ -2,6 +2,7 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const JsonRouteLoader = require('./jsonRouteLoader');
4
4
  const general = require('../libraries/general');
5
+ const { config: configStore } = require('../stores/config');
5
6
 
6
7
  /**
7
8
  * Automatically loads routes from the routes directory
@@ -11,6 +12,7 @@ const general = require('../libraries/general');
11
12
  * @param {boolean} includeAuto - Whether to include auto routes
12
13
  */
13
14
  async function loadRoutes(fastify, routesDir = './routes', includeCommon = false, workerName) {
15
+
14
16
  const routesPath = path.resolve(routesDir);
15
17
 
16
18
  if (!fs.existsSync(routesPath)) {
@@ -31,17 +33,17 @@ async function loadRoutes(fastify, routesDir = './routes', includeCommon = false
31
33
 
32
34
  for (const group of routeGroups) {
33
35
  const groupPath = path.join(routesPath + '/spec', group);
34
- await loadRouteGroup(fastify, group, groupPath, workerName);
36
+ await loadRouteGroup(fastify, group, groupPath, workerName, 'service');
35
37
  }
36
38
 
37
39
  // Load JSON routes from routes/admin folder
38
40
  const jsonLoader = new JsonRouteLoader();
39
- await jsonLoader.loadJsonRoutes(fastify, routesPath + '/auto', workerName);
41
+ await jsonLoader.loadJsonRoutes(fastify, routesPath + '/auto', workerName, 'service');
40
42
 
41
43
  // // Load common routes if requested
42
44
  if (includeCommon) {
43
- const commonPath = path.resolve(__dirname + '/../routes/common');
44
-
45
+ const commonPath = path.resolve('./routes/common');
46
+
45
47
  if (fs.existsSync(commonPath)) {
46
48
 
47
49
  if(!fs.existsSync(commonPath + '/spec')) {
@@ -56,10 +58,10 @@ async function loadRoutes(fastify, routesDir = './routes', includeCommon = false
56
58
 
57
59
  for (const group of routeGroupsCommon) {
58
60
  const groupPath = path.join(commonPath + '/spec', group);
59
- await loadRouteGroup(fastify, group, groupPath, workerName);
61
+ await loadRouteGroup(fastify, group, groupPath, workerName, 'common');
60
62
  }
61
63
 
62
- await jsonLoader.loadJsonRoutes(fastify, commonPath + '/auto', workerName);
64
+ await jsonLoader.loadJsonRoutes(fastify, commonPath + '/auto', workerName, 'common');
63
65
  }
64
66
  }
65
67
  }
@@ -70,8 +72,8 @@ async function loadRoutes(fastify, routesDir = './routes', includeCommon = false
70
72
  * @param {string} groupName - Name of the route group
71
73
  * @param {string} groupPath - Path to the route group directory
72
74
  */
73
- async function loadRouteGroup(fastify, groupName, groupPath, workerName) {
74
- await loadRouteModule(fastify, groupName, groupPath, workerName);
75
+ async function loadRouteGroup(fastify, groupName, groupPath, workerName, type = '--') {
76
+ await loadRouteModule(fastify, groupName, groupPath, workerName, type);
75
77
  }
76
78
 
77
79
  /**
@@ -81,7 +83,7 @@ async function loadRouteGroup(fastify, groupName, groupPath, workerName) {
81
83
  * @param {string} moduleName - Name of the route module
82
84
  * @param {string} modulePath - Path to the route module directory
83
85
  */
84
- async function loadRouteModule(fastify, groupName, modulePath, workerName) {
86
+ async function loadRouteModule(fastify, groupName, modulePath, workerName, type = '--') {
85
87
 
86
88
  // Check if route file exists
87
89
  if (!fs.existsSync(modulePath)) {
@@ -106,14 +108,16 @@ async function loadRouteModule(fastify, groupName, modulePath, workerName) {
106
108
  console.warn(`Route handler is required for route ${route.path}`);
107
109
  continue;
108
110
  }
111
+
112
+ const prefix = configStore.prefix ? configStore.prefix + '/' : '';
109
113
 
110
114
  if(route.path) {
111
- route.path = `/${moduleName}/${route.path}`;
115
+ route.path = `/${prefix}${moduleName}/${route.path}`;
112
116
  } else {
113
- route.path = `/${moduleName}`;
117
+ route.path = `/${prefix}${moduleName}`;
114
118
  }
115
119
 
116
- await registerRoute(fastify, route, moduleName, workerName);
120
+ await registerRoute(fastify, route, moduleName, workerName, type);
117
121
  }
118
122
  }
119
123
 
@@ -130,7 +134,7 @@ async function loadRouteModule(fastify, groupName, modulePath, workerName) {
130
134
  * @param {Object} validation - Route validation
131
135
  * @param {string} prefix - Route prefix
132
136
  */
133
- async function registerRoute(fastify, routeConfig, moduleName, workerName) {
137
+ async function registerRoute(fastify, routeConfig, moduleName, workerName, type = '--') {
134
138
  const {
135
139
  method = 'GET',
136
140
  path = '',
@@ -143,20 +147,8 @@ async function registerRoute(fastify, routeConfig, moduleName, workerName) {
143
147
  // Build full URL
144
148
  const fullUrl = path;
145
149
 
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
150
  const schemaRoute = {
159
- tags: [general.titleCase(moduleName)],
151
+ tags: [(type == 'service' ? general.titleCase(configStore.name) + ' | ' : 'Common | ') + 'Specs | ' + general.titleCase(moduleName)],
160
152
  response: {
161
153
  200: schema.success_response ? { ...schema.success_response, additionalProperties: schema.success_response.additionalProperties || false } : {
162
154
  type: 'object',
@@ -209,6 +201,38 @@ async function registerRoute(fastify, routeConfig, moduleName, workerName) {
209
201
  schemaRoute.security = [{ bearer: [] }];
210
202
  }
211
203
 
204
+ if(schema.pagination) {
205
+
206
+ if(schemaRoute.querystring && schemaRoute.querystring.properties) {
207
+ schemaRoute.querystring.properties = Object.assign(schemaRoute.querystring.properties || {}, {
208
+ page: { type: 'integer', minimum: 1, default: 1 },
209
+ limit: { type: 'integer', minimum: 1, maximum: 100, default: 25 },
210
+ search: { type: 'string' },
211
+ order: { type: 'string' }
212
+ });
213
+ } else {
214
+ schemaRoute.querystring = {
215
+ type: 'object',
216
+ properties: {
217
+ page: { type: 'integer', minimum: 1, default: 1 },
218
+ limit: { type: 'integer', minimum: 1, maximum: 100, default: 25 },
219
+ search: { type: 'string' },
220
+ order: { type: 'string' }
221
+ }
222
+ }
223
+ }
224
+
225
+ schemaRoute.response[200].properties.pagination = {
226
+ type: 'object',
227
+ properties: {
228
+ total: { type: 'integer' },
229
+ page: { type: 'integer' },
230
+ limit: { type: 'integer' },
231
+ totalPages: { type: 'integer' }
232
+ }
233
+ }
234
+ }
235
+
212
236
  // Register the route
213
237
  fastify.route({
214
238
  method: method.toUpperCase(),