@ecopex/ecopex-framework 1.0.13 → 1.0.15

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
@@ -17,25 +17,25 @@ module.exports = {
17
17
  produces: ['application/json'],
18
18
  components: {
19
19
  securitySchemes: {
20
- bearer: {
21
- type: 'http',
22
- scheme: 'bearer',
23
- bearerFormat: 'JWT',
24
- description: 'Enter the token with the `Bearer: ` prefix, e.g. "Bearer abcde12345".'
25
- },
26
- apiKey: {
27
- type: 'apiKey',
28
- in: 'header',
29
- name: 'apiKey',
30
- description: 'Enter token e.g. "abcde1-234533-333312-2222".'
31
- }
20
+ bearer: {
21
+ type: 'http',
22
+ scheme: 'bearer',
23
+ bearerFormat: 'JWT',
24
+ description: 'Enter the token with the `Bearer: ` prefix, e.g. "Bearer abcde12345".'
25
+ },
26
+ apiKey: {
27
+ type: 'apiKey',
28
+ in: 'header',
29
+ name: 'apiKey',
30
+ description: 'Enter token e.g. "abcde1-234533-333312-2222".'
31
+ }
32
32
  },
33
33
  headers: {
34
- description: 'Headers',
35
- scheme: {
36
- type: 'object',
37
- properties: {}
38
- }
34
+ description: 'Headers',
35
+ scheme: {
36
+ type: 'object',
37
+ properties: {}
38
+ }
39
39
  }
40
40
  }
41
41
  }
package/index.js CHANGED
@@ -4,11 +4,13 @@ const JWT = require('./libraries/jwt');
4
4
  const BCRYPT = require('./libraries/bcrypt');
5
5
  const date = require('./libraries/date');
6
6
  const twofactor = require('./libraries/2fa');
7
+ const i18n = require('./utils/i18n');
7
8
 
8
9
  const jwt = new JWT();
9
10
  const bcrypt = new BCRYPT();
10
11
 
11
12
  const start = async (config) => {
13
+ i18n.setConfig(config);
12
14
  await knex.initialize(config);
13
15
  await fastifyStart(config);
14
16
  }
@@ -19,5 +21,6 @@ module.exports = {
19
21
  date: date,
20
22
  jwt: jwt,
21
23
  bcrypt: bcrypt,
22
- twofactor: twofactor
24
+ twofactor: twofactor,
25
+ i18n: i18n
23
26
  }
package/knexfile.js CHANGED
@@ -1 +1,47 @@
1
- module.exports = require('./config/database');
1
+ const config = require('../../../config');
2
+
3
+ const services = Object.entries(config).filter(([, value]) => value.database).map(([key, ]) => key);
4
+
5
+ let readline = require('readline-sync');
6
+ let service = readline.question("For which service do you want to migrate? (" + services.join(', ') + ') default:' + services[0] + ' => ');
7
+ service = service || services[0];
8
+
9
+ if(!services.includes(service)) {
10
+ console.log('Service not found');
11
+ process.exit(1);
12
+ }
13
+
14
+ module.exports = {
15
+ development: {
16
+ client: 'mysql2',
17
+ connection: {
18
+ host: config[service].database.host,
19
+ port: config[service].database.port,
20
+ user: config[service].database.user,
21
+ password: config[service].database.password,
22
+ database: config[service].database.database
23
+ },
24
+ migrations: {
25
+ directory: '../../../database/' + service + '/migrations'
26
+ },
27
+ seeds: {
28
+ directory: '../../../database/' + service + '/seeds'
29
+ }
30
+ },
31
+ production: {
32
+ client: 'mysql2',
33
+ connection: {
34
+ host: config[service].database.host,
35
+ port: config[service].database.port,
36
+ user: config[service].database.user,
37
+ password: config[service].database.password,
38
+ database: config[service].database.database
39
+ },
40
+ migrations: {
41
+ directory: '../../../database/' + service + '/migrations'
42
+ },
43
+ seeds: {
44
+ directory: '../../../database/' + service + '/seeds'
45
+ }
46
+ }
47
+ };
@@ -21,7 +21,8 @@ async function registerPlugins(config) {
21
21
  // CORS plugin
22
22
  await fastifyInstance.register(require('@fastify/cors'), {
23
23
  origin: config.cors || '*',
24
- credentials: true
24
+ methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
25
+ allowedHeaders: ['Content-Type', 'Authorization', 'Accept', 'locale', 'timezone', 'device']
25
26
  });
26
27
 
27
28
  // Swagger documentation
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopex/ecopex-framework",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "description": "Javascript Framework for API and Admin Panel",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -48,7 +48,8 @@
48
48
  "pm2": "^5.3.0",
49
49
  "qrcode": "^1.5.4",
50
50
  "slugify": "^1.6.6",
51
- "uuid": "^9.0.1"
51
+ "uuid": "^9.0.1",
52
+ "readline-sync": "^1.4.10"
52
53
  },
53
54
  "devDependencies": {
54
55
  "nodemon": "^3.0.1",
@@ -1,10 +1,11 @@
1
1
  const knex = require('../../libraries/knex');
2
2
  const BCRYPT = require('../../libraries/bcrypt');
3
+ const returnSlug = require('../../libraries/slug');
3
4
  const bcrypt = new BCRYPT();
4
5
  const i18n = require('../../utils/i18n');
5
6
  const { uploadFile } = require('../../libraries/upload');
6
7
  const { randomChars } = require('../../libraries/controls');
7
- const { get_all, get_by_primary_key, create_item, update_item, delete_item } = require('../../stores');
8
+ const { get_all, get_by_primary_key, create_item, update_item, delete_item, stores } = require('../../stores');
8
9
  const { config } = require('../../stores/config');
9
10
  /**
10
11
  * Get all records with pagination, search, and join tables
@@ -25,8 +26,9 @@ async function getAll(request, reply, routeOptions = {}) {
25
26
  const tableName = routeOptions.tableName || 'users';
26
27
  const routeConfig = routeOptions.routeConfig || {};
27
28
  const primaryKey = routeOptions.primaryKey || 'id';
28
- const filters = routeOptions.filters || {};
29
- const joinTables = routeOptions.joinTables || {};
29
+ const filters = routeOptions.filters || false;
30
+ const owned = routeOptions.owned || false;
31
+ const joinTables = routeOptions.joinTables || false;
30
32
  const splitOrder = order.split(':');
31
33
  const orderBy = `${tableName}.${(splitOrder[0] || primaryKey)}`;
32
34
  const orderDirection = splitOrder[1] || 'desc';
@@ -46,18 +48,51 @@ async function getAll(request, reply, routeOptions = {}) {
46
48
  });
47
49
  }
48
50
 
49
- if(routeConfig.owned) {
50
- query.where(routeConfig.owned, request.user?.id || 0);
51
- query.where(routeConfig.owned_type, request.user?.type || 'user');
51
+ if(owned) {
52
+ for(let i = 0; i < Object.entries(owned).length; i++) {
53
+ const owned_field = Object.entries(owned)[i];
54
+ if(owned_field[1].from == 'request') {
55
+ query.where(tableName + '.' + owned_field[0], (owned_field[1].operator || '='), getText(request, owned_field[1].path));
56
+ } else {
57
+ query.where(tableName + '.' + owned_field[0], (owned_field[1].operator || '='), owned_field[1].value);
58
+ }
59
+ }
60
+ }
61
+
62
+ if(routeConfig.conditions) {
63
+ for(let i = 0; i < Object.entries(routeConfig.conditions).length; i++) {
64
+ const condition = Object.entries(routeConfig.conditions)[i];
65
+ if(condition[1].value_type == 'request') {
66
+ query.where(condition[0], (condition[1].operator || '='), getText(request, condition[1].value));
67
+ } else {
68
+ query.where(condition[0], (condition[1].operator || '='), condition[1].value);
69
+ }
70
+ }
52
71
  }
53
72
 
54
73
  if(joinTables) {
55
74
  for(let i = 0; i < Object.values(joinTables).length; i++) {
56
75
  const tableKey = Object.keys(joinTables)[i];
57
76
  let joinTable = Object.values(joinTables)[i];
58
- if(joinTable.type == 'one-to-one') {
59
- if(joinTable.extra_where) {
60
- query.leftJoin(joinTable.table + ' as ' + tableKey, `${tableName}.${joinTable.foreign_key} = ${tableKey}.${joinTable.primary_key} AND ${joinTable.extra_where}`);
77
+ if(joinTable.type == 'one-to-one' || (joinTable.type == 'one-to-many' && joinTable.localized)) {
78
+ if(joinTable.localized) {
79
+ const stored_languages = stores.languages || false;
80
+
81
+ if(stored_languages) {
82
+ const find_language = [...stored_languages.data.values()].find(n => n.code === request.locale);
83
+
84
+ if(find_language) {
85
+ if(joinTable.extra_where) {
86
+ joinTable.extra_where += ` AND ${tableKey}.language_id = ${find_language.language_id}`;
87
+ } else {
88
+ joinTable.extra_where = `${tableKey}.language_id = ${find_language.language_id}`;
89
+ }
90
+ }
91
+ }
92
+ }
93
+
94
+ if(joinTable.extra_where && joinTable.extra_where != '') {
95
+ query.leftJoin(joinTable.table + ' as ' + tableKey, knex.connection.db.raw(`${tableName}.${joinTable.foreign_key} = ${tableKey}.${joinTable.primary_key} AND ${joinTable.extra_where}`));
61
96
  } else {
62
97
  query.leftJoin(joinTable.table + ' as ' + tableKey, `${tableName}.${joinTable.foreign_key}`, `${tableKey}.${joinTable.primary_key}`);
63
98
  }
@@ -150,9 +185,21 @@ async function getById(request, reply, routeOptions = {}, inner = false) {
150
185
  try {
151
186
  const tableName = routeOptions.tableName || 'users';
152
187
  const joinTables = routeOptions.joinTables || {};
188
+ const owned = routeOptions.owned || false;
153
189
  const item_query = knex.connection.db(tableName)
154
190
  .select(`${tableName}.*`)
155
191
  .where(request.params);
192
+
193
+ if(owned) {
194
+ for(let i = 0; i < Object.entries(owned).length; i++) {
195
+ const owned_field = Object.entries(owned)[i];
196
+ if(owned_field[1].from == 'request') {
197
+ item_query.where(tableName + '.' + owned_field[0], (owned_field[1].operator || '='), getText(request, owned_field[1].path));
198
+ } else {
199
+ item_query.where(tableName + '.' + owned_field[0], (owned_field[1].operator || '='), owned_field[1].value);
200
+ }
201
+ }
202
+ }
156
203
 
157
204
  if(joinTables) {
158
205
  for(let i = 0; i < Object.values(joinTables).length; i++) {
@@ -173,8 +220,21 @@ async function getById(request, reply, routeOptions = {}, inner = false) {
173
220
 
174
221
  const item_data = await item_query.first();
175
222
 
176
- let item = {}
223
+ if(!item_data) {
224
+ if(!inner) {
225
+ return reply.status(404).send({
226
+ status: false,
227
+ message: request.t('messages.record_not_found')
228
+ });
229
+ } else {
230
+ return {
231
+ status: false,
232
+ message: request.t('messages.record_not_found')
233
+ };
234
+ }
235
+ }
177
236
 
237
+ let item = {}
178
238
 
179
239
  if(joinTables) {
180
240
  item = {
@@ -187,6 +247,14 @@ async function getById(request, reply, routeOptions = {}, inner = false) {
187
247
  item[join_table[0]] = item_data[join_table[0]];
188
248
  }
189
249
  }
250
+ const join_one_to_many = Object.entries(joinTables).filter(n => n[1].type === 'one-to-many');
251
+ if(join_one_to_many.length > 0) {
252
+ for(let i = 0; i < join_one_to_many.length; i++) {
253
+ const join_table = join_one_to_many[i];
254
+ const join_table_data = await knex.connection.db(join_table[1].table).where(join_table[1].foreign_key, item_data[tableName][join_table[1].primary_key]);
255
+ item[join_table[0]] = join_table_data;
256
+ }
257
+ }
190
258
  } else {
191
259
  item = item_data;
192
260
  }
@@ -242,34 +310,61 @@ async function getById(request, reply, routeOptions = {}, inner = false) {
242
310
  */
243
311
  async function create(request, reply, routeOptions = {}) {
244
312
 
313
+ const tableName = routeOptions.tableName || 'users';
314
+ const primaryKey = routeOptions.primaryKey || 'id';
315
+ const owned = routeOptions.owned || false;
316
+ const schema = routeOptions.schema || {};
317
+
318
+ if(routeOptions.unique && routeOptions.unique.length > 0) {
319
+ const extra_where = routeOptions.unique.map(n => `${tableName}.${n} = ${request.body[n]}`).join(' AND ');
320
+ const check_unique = await checkUnique(null, null, tableName, extra_where);
321
+ if(check_unique) {
322
+ return reply.status(400).send({
323
+ status: false,
324
+ message: request.t('messages.unique_constraint_violation')
325
+ });
326
+ }
327
+ }
328
+
245
329
  if(routeOptions.store) {
246
330
  return create_item(routeOptions.store, request.body, reply, routeOptions);
247
331
  }
248
332
 
249
333
  try {
250
- const tableName = routeOptions.tableName || 'users';
251
- const primaryKey = routeOptions.primaryKey || 'id';
252
- const schema = routeOptions.schema || {};
253
334
 
254
335
  // Use payload field if available, otherwise use request.body directly
255
336
  let data = request.body;
256
337
 
257
338
  if (schema) {
258
- const payloadFields = Object.entries(schema).filter(n => n[1].creatable || n[1].generatable);
339
+ const payloadFields = Object.entries(schema).filter(n => n[1].creatable || n[1].generatable || n[1].slug);
259
340
  data = {};
260
341
 
261
342
  for (const field of payloadFields) {
262
- if (request.body[field[0]] !== undefined || schema[field[0]].generatable) {
343
+ if (request.body[field[0]] !== undefined || schema[field[0]].generatable || schema[field[0]].slug) {
263
344
  if (schema[field[0]].crypted) {
264
345
  data[field[0]] = await bcrypt.hash(request.body[field[0]]);
265
346
  } else if(schema[field[0]].generatable) {
266
347
  data[field[0]] = await generateRandomString(typeof schema[field[0]].generatable === 'string' ? request.body[schema[field[0]].generatable] : null, field[0], tableName);
348
+ } else if(schema[field[0]].slug) {
349
+ const extra_where = schema[field[0]].slug.unique.map(n => `${tableName}.${n} = ${request.body[n]}`).join(' AND ');
350
+ data[field[0]] = await generateSlug(request.body[schema[field[0]].slug.from], field[0], tableName, extra_where, schema[field[0]].slug.random_chars || 5);
267
351
  } else {
268
352
  data[field[0]] = request.body[field[0]];
269
353
  }
270
354
  }
271
355
  }
272
356
  }
357
+
358
+ if(owned) {
359
+ for(let i = 0; i < Object.entries(owned).length; i++) {
360
+ const owned_field = Object.entries(owned)[i];
361
+ if(owned_field[1].from == 'request') {
362
+ data[owned_field[0]] = getText(request, owned_field[1].path);
363
+ } else {
364
+ data[owned_field[0]] = owned_field[1].value;
365
+ }
366
+ }
367
+ }
273
368
 
274
369
  try {
275
370
  const [id] = await knex.connection.db(tableName)
@@ -520,6 +615,19 @@ const generateRandomString = async (from_string = null, field_name = null, table
520
615
  return random_string;
521
616
  }
522
617
 
618
+ const generateSlug = async (from_string = null, field_name = null, tableName = null, extra_where = null, random_chars = 5) => {
619
+ let slug = returnSlug(from_string);
620
+
621
+ const existing_item = await checkUnique(slug, field_name, tableName, extra_where);
622
+
623
+ if(existing_item) {
624
+ slug = returnSlug(from_string + '-' + randomChars(random_chars));
625
+ return generateSlug(slug, field_name, tableName, extra_where, random_chars);
626
+ }
627
+
628
+ return slug;
629
+ }
630
+
523
631
  /**
524
632
  * Check if the value is unique
525
633
  * @param {string} value - The value to check unique
@@ -527,9 +635,16 @@ const generateRandomString = async (from_string = null, field_name = null, table
527
635
  * @param {string} tableName - The table name to check unique
528
636
  * @returns {Promise<boolean>} - The unique status
529
637
  */
530
- const checkUnique = async (value = null, field_name = null, tableName = null) => {
531
- const existing_item = await knex.connection.db(tableName).select(field_name).where(field_name, value).first();
532
- return existing_item ? true : false;
638
+ const checkUnique = async (value = null, field_name = null, tableName = null, extra_where = null) => {
639
+ let query = knex.connection.db(tableName).select(field_name);
640
+ if(value && field_name) {
641
+ query.where(field_name, value);
642
+ }
643
+ if(extra_where) {
644
+ query.where(knex.connection.db.raw(extra_where));
645
+ }
646
+ const existing_item = await query;
647
+ return existing_item.length > 0 ? true : false;
533
648
  }
534
649
 
535
650
  /**
@@ -590,7 +705,7 @@ const processJoinTables = async (items, joinTables) => {
590
705
  return items;
591
706
  }
592
707
 
593
- const length_of_one_to_many = Object.entries(joinTables).filter(n => n[1].type === 'one-to-many');
708
+ const length_of_one_to_many = Object.entries(joinTables).filter(n => n[1].type === 'one-to-many' && !n[1].localized);
594
709
  if(length_of_one_to_many.length > 0) {
595
710
  for(let i = 0; i < length_of_one_to_many.length; i++) {
596
711
  const joinTable = length_of_one_to_many[i][1];
@@ -601,7 +716,7 @@ const processJoinTables = async (items, joinTables) => {
601
716
  const personal_items = knex.connection.db(joinTable.table).whereIn(joinTable.table + '.' + joinTable.foreign_key, personal_ids);
602
717
 
603
718
  if(joinTable.extra_where) {
604
- personal_items.where(db.raw(joinTable.table + '.' + joinTable.extra_where));
719
+ personal_items.where(knex.connection.db.raw(joinTable.table + '.' + joinTable.extra_where));
605
720
  }
606
721
 
607
722
  if(joinTable.join_table) {
@@ -628,6 +743,17 @@ const processJoinTables = async (items, joinTables) => {
628
743
  }
629
744
  }
630
745
 
746
+ const getText = (row, path) => {
747
+ const keys = path.split('.')
748
+ let value = row
749
+
750
+ for (const key of keys) {
751
+ value = value[key] || false
752
+ }
753
+
754
+ return value
755
+ }
756
+
631
757
  module.exports = {
632
758
  getAll,
633
759
  getById,
package/utils/i18n.js CHANGED
@@ -1,6 +1,5 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const { config } = require('../stores/config');
4
3
 
5
4
  /**
6
5
  * Internationalization utility for multi-language support
@@ -8,13 +7,18 @@ const { config } = require('../stores/config');
8
7
  class I18n {
9
8
  constructor() {
10
9
  this.locales = {};
11
- this.defaultLocale = config.default_language || 'en';
12
- this.supportedLocales = config.supported_languages || ['en'];
10
+ this.defaultLocale = 'en';
11
+ this.supportedLocales = ['en'];
13
12
  this.localesPath = path.join(__dirname, '../locales');
14
13
  this.localesBackend = path.join('./locales');
15
14
  this.loadLocales();
16
15
  }
17
16
 
17
+ setConfig(config) {
18
+ this.defaultLocale = config.default_locale || 'en';
19
+ this.supportedLocales = config.supported_locales || ['en'];
20
+ }
21
+
18
22
  /**
19
23
  * Load all locale files
20
24
  */
@@ -62,15 +66,30 @@ class I18n {
62
66
  * @param {Object} params - Parameters to replace in the translation
63
67
  * @returns {string} Translated string
64
68
  */
65
- t(key, locale = this.defaultLocale, params = {}) {
69
+ t(key, params = {}, locale = this.defaultLocale) {
66
70
  const keys = key.split('.');
67
- let translation = this.locales[locale] || this.locales[this.defaultLocale];
71
+
72
+ if(!this.locales[locale]) {
73
+ this.locales[locale] = {};
74
+ }
75
+
76
+ if(!fs.existsSync(path.join(this.localesBackend, locale))) {
77
+ // create directory
78
+ fs.mkdirSync(path.join(this.localesBackend, locale), { recursive: true });
79
+ }
80
+
81
+ let translation = this.locales[locale];
68
82
  const fileName = keys.length > 0 ? keys[0] : 'index';
69
83
 
70
84
  if(!translation[fileName]) {
71
85
  translation[fileName] = {};
72
86
  }
73
87
 
88
+ if(!fs.existsSync(path.join(this.localesBackend, locale, `${fileName}.json`))) {
89
+ this.writeFile(key, locale, params);
90
+ return key;
91
+ }
92
+
74
93
  if(keys.length > 1) {
75
94
  for(const k of keys.slice(1)) {
76
95
  if(translation[fileName] && translation[fileName][k] !== undefined) {
@@ -13,7 +13,7 @@ class JsonRouteLoader {
13
13
  constructor() {
14
14
  this.supportedRoutes = ['list', 'get', 'create', 'update', 'delete', 'upload'];
15
15
  this.registeredRoutes = new Set();
16
- this.deleteKeys = ['creatable', 'updatable', 'search_type', 'listable', 'crypted', 'create_required', 'generatable', 'unique', 'example'];
16
+ this.deleteKeys = ['creatable', 'updatable', 'search_type', 'listable', 'crypted', 'create_required', 'generatable', 'unique', 'example', 'slug', 'exists'];
17
17
  }
18
18
 
19
19
  /**
@@ -67,6 +67,8 @@ class JsonRouteLoader {
67
67
  const store = config.store || null;
68
68
  const routes = config.routes || {};
69
69
  const schema = config.schema || {};
70
+ const unique = config.unique || [];
71
+ const owned = config.owned || {};
70
72
  const joinTables = config.join_tables || {};
71
73
 
72
74
  // Validate configuration
@@ -77,7 +79,7 @@ class JsonRouteLoader {
77
79
  const routeName = route.action;
78
80
  const routeConfig = route;
79
81
  if (this.supportedRoutes.includes(routeName)) {
80
- await this.registerRoute(fastify, tableName, routeName, routeConfig, primaryKey, workerName, fileName, store, schema, joinTables, type);
82
+ await this.registerRoute(fastify, tableName, routeName, routeConfig, primaryKey, workerName, fileName, store, schema, joinTables, type, unique, owned);
81
83
  if(store) {
82
84
  await add_model(store, primaryKey, schema);
83
85
  }
@@ -113,7 +115,7 @@ class JsonRouteLoader {
113
115
  * @param {string} routeName - Route name (list, get, create, update, delete)
114
116
  * @param {Object} routeConfig - Route configuration
115
117
  */
116
- async registerRoute(fastify, tableName, routeName, routeConfig, primaryKey, workerName, fileName, store, schema, joinTables, type = 'service') {
118
+ async registerRoute(fastify, tableName, routeName, routeConfig, primaryKey, workerName, fileName, store, schema, joinTables, type = 'service', unique = [], owned = {}) {
117
119
 
118
120
  const routeDefinition = this.generateRouteDefinition(tableName, routeName, routeConfig, primaryKey, fileName, schema, type, joinTables);
119
121
  const routeKey = `${routeDefinition.method}:${routeDefinition.url}`;
@@ -150,7 +152,7 @@ class JsonRouteLoader {
150
152
  },
151
153
  handler: async (request, reply) => {
152
154
  // Pass table name and route config to handler as additional parameters
153
- return handler(request, reply, { tableName, routeConfig, schema, primaryKey, store, filters, joinTables });
155
+ return handler(request, reply, { tableName, routeConfig, schema, primaryKey, store, filters, joinTables, unique, owned });
154
156
  }
155
157
  });
156
158
 
@@ -170,9 +172,17 @@ class JsonRouteLoader {
170
172
  */
171
173
  generateRouteDefinition(tableName, routeName, routeConfig, primaryKey, fileName, schema, type = 'service', joinTables = {}) {
172
174
  const prefix = configStore.prefix ? configStore.prefix + '/' : '';
173
- const baseUrl = `/${prefix}${fileName}`;
175
+ let cleanPath = routeConfig.path || false
176
+ if(cleanPath && cleanPath.startsWith('/')) {
177
+ cleanPath = cleanPath.slice(1);
178
+ }
179
+ let baseUrl = `/${prefix}${(cleanPath ? cleanPath : fileName)}`;
174
180
  primaryKey = primaryKey || 'id';
175
181
 
182
+ if(!routeConfig.path) {
183
+ baseUrl += `/${primaryKey}`;
184
+ }
185
+
176
186
  const routeDefinitions = {
177
187
  list: {
178
188
  method: 'GET',
@@ -181,7 +191,7 @@ class JsonRouteLoader {
181
191
  },
182
192
  get: {
183
193
  method: 'GET',
184
- url: `${baseUrl}/:${primaryKey}`,
194
+ url: `${baseUrl}`,
185
195
  schema: this.generateGetSchema(tableName, routeConfig, primaryKey, schema, type, joinTables)
186
196
  },
187
197
  create: {
@@ -191,17 +201,17 @@ class JsonRouteLoader {
191
201
  },
192
202
  update: {
193
203
  method: 'PUT',
194
- url: `${baseUrl}/:${primaryKey}`,
204
+ url: `${baseUrl}`,
195
205
  schema: this.generateUpdateSchema(tableName, routeConfig, primaryKey, schema, type, joinTables)
196
206
  },
197
207
  delete: {
198
208
  method: 'DELETE',
199
- url: `${baseUrl}/:${primaryKey}`,
209
+ url: `${baseUrl}`,
200
210
  schema: this.generateDeleteSchema(tableName, routeConfig, primaryKey, schema, type, joinTables)
201
211
  },
202
212
  upload: {
203
213
  method: 'POST',
204
- url: `${baseUrl}/:${primaryKey}/${routeConfig.upload_field || 'upload'}`,
214
+ url: `${baseUrl}`,
205
215
  schema: this.generateUploadSchema(tableName, routeConfig, primaryKey, schema, type, joinTables),
206
216
  validatorCompiler: ({ schema }) => {
207
217
  return (data) => {
@@ -224,7 +234,7 @@ class JsonRouteLoader {
224
234
  * @returns {Object} Schema
225
235
  */
226
236
  generateListSchema(tableName, routeConfig, primaryKey, schema, type = 'service', joinTables = {}) {
227
- const itemSchema = this.generateItemSchema(schema, primaryKey, joinTables);
237
+ const itemSchema = this.generateItemSchema('list', schema, primaryKey, joinTables);
228
238
 
229
239
  return {
230
240
  description: `Get all ${tableName} with pagination and search`,
@@ -239,7 +249,7 @@ class JsonRouteLoader {
239
249
  order: { type: 'string', default: `${primaryKey}:desc` },
240
250
  ...Object.entries(schema).reduce((acc, [key, value]) => {
241
251
  if(value.search_type) {
242
- acc[key] = { type: value.type, default: value.default || undefined, enum: value.enum || undefined };
252
+ acc[key] = { type: value.type == 'boolean' ? 'integer' : value.type, default: value.default || undefined, enum: value.enum || undefined };
243
253
  }
244
254
  return acc;
245
255
  }, {})
@@ -283,7 +293,7 @@ class JsonRouteLoader {
283
293
  * @returns {Object} Schema
284
294
  */
285
295
  generateGetSchema(tableName, routeConfig, primaryKey, schema, type = 'service', joinTables = {}) {
286
- const itemSchema = this.generateItemSchema(schema, primaryKey, joinTables);
296
+ const itemSchema = this.generateItemSchema('get', schema, primaryKey, joinTables);
287
297
  primaryKey = primaryKey || 'id';
288
298
 
289
299
  return {
@@ -336,7 +346,7 @@ class JsonRouteLoader {
336
346
  type: 'object',
337
347
  properties: {
338
348
  status: { type: 'boolean', default: true },
339
- data: this.generateItemSchema(schema, primaryKey, joinTables)
349
+ data: this.generateItemSchema('create', schema, primaryKey, joinTables)
340
350
  },
341
351
  additionalProperties: false
342
352
  },
@@ -378,7 +388,7 @@ class JsonRouteLoader {
378
388
  type: 'object',
379
389
  properties: {
380
390
  status: { type: 'boolean', default: true },
381
- data: this.generateItemSchema(schema, primaryKey, joinTables)
391
+ data: this.generateItemSchema('update', schema, primaryKey, joinTables)
382
392
  },
383
393
  additionalProperties: false
384
394
  },
@@ -417,7 +427,7 @@ class JsonRouteLoader {
417
427
  type: 'object',
418
428
  properties: {
419
429
  status: { type: 'boolean', default: true },
420
- data: this.generateItemSchema(schema, primaryKey, joinTables)
430
+ data: this.generateItemSchema('delete', schema, primaryKey, joinTables)
421
431
  },
422
432
  additionalProperties: false
423
433
  },
@@ -465,7 +475,7 @@ class JsonRouteLoader {
465
475
  type: 'object',
466
476
  properties: {
467
477
  status: { type: 'boolean', default: true },
468
- data: this.generateItemSchema(schema, primaryKey, joinTables)
478
+ data: this.generateItemSchema('upload', schema, primaryKey, joinTables)
469
479
  },
470
480
  additionalProperties: false
471
481
  },
@@ -485,7 +495,7 @@ class JsonRouteLoader {
485
495
  * @param {Object} itemSchema - Item schema from config
486
496
  * @returns {Object} Item schema
487
497
  */
488
- generateItemSchema(schema, primaryKey, joinTables = {}) {
498
+ generateItemSchema(type, schema, primaryKey, joinTables = {}) {
489
499
  if (!schema) {
490
500
  return { type: 'object' };
491
501
  }
@@ -512,12 +522,21 @@ class JsonRouteLoader {
512
522
 
513
523
  if(Object.entries(joinTables).length > 0) {
514
524
  for(const [key, value] of Object.entries(joinTables)) {
515
- if(value.type == 'one-to-one') {
525
+ if(value.type == 'one-to-one' || (value.type == 'one-to-many' && value.localized && type == 'list')) {
516
526
  withoutReferences[value.table] = {
517
527
  type: 'object',
518
528
  properties: value.properties,
519
529
  additionalProperties: false
520
530
  };
531
+ } else if(value.type == 'one-to-many') {
532
+ withoutReferences[value.table] = {
533
+ type: 'array',
534
+ items: {
535
+ type: 'object',
536
+ properties: value.properties,
537
+ additionalProperties: false
538
+ }
539
+ };
521
540
  }
522
541
  }
523
542
  }