@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,587 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const handlers = require('@root/routes/auto/handler');
|
|
4
|
+
const general = require('@root/libraries/general');
|
|
5
|
+
const { add_model } = require('@root/stores');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* JSON Route Loader
|
|
9
|
+
* Loads routes from JSON configuration files in routes/admin folder
|
|
10
|
+
*/
|
|
11
|
+
class JsonRouteLoader {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.supportedRoutes = ['list', 'get', 'create', 'update', 'delete', 'upload'];
|
|
14
|
+
this.registeredRoutes = new Set();
|
|
15
|
+
this.deleteKeys = ['creatable', 'updatable', 'search_type', 'listable', 'crypted', 'create_required', 'generatable', 'unique', 'example'];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Load all JSON route configurations from routes/admin folder
|
|
20
|
+
* @param {Object} fastify - Fastify instance
|
|
21
|
+
*/
|
|
22
|
+
async loadJsonRoutes(fastify, routesDir, workerName) {
|
|
23
|
+
const adminPath = path.resolve(routesDir);
|
|
24
|
+
|
|
25
|
+
if(!workerName) {
|
|
26
|
+
console.warn(workerName + ' Worker name is required');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!fs.existsSync(adminPath)) {
|
|
31
|
+
console.warn(workerName + ' routes directory does not exist auto directory');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Find all JSON files in routes/admin
|
|
36
|
+
const jsonFiles = fs.readdirSync(adminPath, { withFileTypes: true })
|
|
37
|
+
.filter(dirent => dirent.isFile() && dirent.name.endsWith('.json'))
|
|
38
|
+
.map(dirent => dirent.name);
|
|
39
|
+
|
|
40
|
+
for (const jsonFile of jsonFiles) {
|
|
41
|
+
try {
|
|
42
|
+
await this.loadJsonRoute(fastify, path.join(adminPath, jsonFile), workerName);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.log(error.message);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Load a single JSON route configuration
|
|
51
|
+
* @param {Object} fastify - Fastify instance
|
|
52
|
+
* @param {string} jsonPath - Path to JSON file
|
|
53
|
+
*/
|
|
54
|
+
async loadJsonRoute(fastify, jsonPath, workerName) {
|
|
55
|
+
try {
|
|
56
|
+
|
|
57
|
+
const config = require(jsonPath);
|
|
58
|
+
if(!config) {
|
|
59
|
+
console.warn(`Config not found for ${jsonPath}`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const fileName = jsonPath.split('/').pop().split('.')[0];
|
|
64
|
+
const tableName = config.table;
|
|
65
|
+
const primaryKey = config.primary_key || 'id';
|
|
66
|
+
const store = config.store || null;
|
|
67
|
+
const routes = config.routes || {};
|
|
68
|
+
const schema = config.schema || {};
|
|
69
|
+
|
|
70
|
+
// Validate configuration
|
|
71
|
+
this.validateConfig(config);
|
|
72
|
+
|
|
73
|
+
// Register routes
|
|
74
|
+
for (const route of routes) {
|
|
75
|
+
const routeName = route.action;
|
|
76
|
+
const routeConfig = route;
|
|
77
|
+
if (this.supportedRoutes.includes(routeName)) {
|
|
78
|
+
await this.registerRoute(fastify, tableName, routeName, routeConfig, primaryKey, workerName, fileName, store, schema);
|
|
79
|
+
if(store) {
|
|
80
|
+
await add_model(store, primaryKey, schema);
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
fastify.log.warn(`Unsupported route type: ${routeName} for table: ${tableName}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.log(error);
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Validate JSON configuration
|
|
95
|
+
* @param {Object} config - Configuration object
|
|
96
|
+
*/
|
|
97
|
+
validateConfig(config) {
|
|
98
|
+
if (!config.table) {
|
|
99
|
+
throw new Error('Table name is required');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!config.routes || typeof config.routes !== 'object') {
|
|
103
|
+
throw new Error('Routes object is required');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Register a route for a specific table and route type
|
|
109
|
+
* @param {Object} fastify - Fastify instance
|
|
110
|
+
* @param {string} tableName - Table name
|
|
111
|
+
* @param {string} routeName - Route name (list, get, create, update, delete)
|
|
112
|
+
* @param {Object} routeConfig - Route configuration
|
|
113
|
+
*/
|
|
114
|
+
async registerRoute(fastify, tableName, routeName, routeConfig, primaryKey, workerName, fileName, store, schema) {
|
|
115
|
+
|
|
116
|
+
const routeDefinition = this.generateRouteDefinition(tableName, routeName, routeConfig, primaryKey, fileName, schema);
|
|
117
|
+
const routeKey = `${routeDefinition.method}:${routeDefinition.url}`;
|
|
118
|
+
|
|
119
|
+
if (this.registeredRoutes.has(routeKey)) {
|
|
120
|
+
console.log(`Route already registered, skipping: ${routeKey}`);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
this.registeredRoutes.add(routeKey);
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
|
|
128
|
+
const handler = this.getHandler(routeName);
|
|
129
|
+
|
|
130
|
+
if(routeConfig.security) {
|
|
131
|
+
routeDefinition.schema.security = [{ bearer: [] }];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const filters = Object.entries(schema || {}).reduce((acc, [key, value]) => {
|
|
135
|
+
if(value.search_type) {
|
|
136
|
+
acc[key] = value;
|
|
137
|
+
}
|
|
138
|
+
return acc;
|
|
139
|
+
}, {});
|
|
140
|
+
|
|
141
|
+
// Register the route
|
|
142
|
+
fastify.route({
|
|
143
|
+
...routeDefinition,
|
|
144
|
+
preHandler: async (request, reply, done) => {
|
|
145
|
+
if(routeConfig.security) {
|
|
146
|
+
await require(`@root/routes/${workerName}/middlewares`)[routeConfig.security](request, reply);
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
handler: async (request, reply) => {
|
|
150
|
+
// Pass table name and route config to handler as additional parameters
|
|
151
|
+
return handler(request, reply, { tableName, routeConfig, schema, primaryKey, store, filters });
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.error(`Error in registerRoute for ${tableName}/${routeName}:`, error.message);
|
|
157
|
+
console.error(`Error details:`, error);
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Generate route definition
|
|
164
|
+
* @param {string} tableName - Table name
|
|
165
|
+
* @param {string} routeName - Route name
|
|
166
|
+
* @param {Object} routeConfig - Route configuration
|
|
167
|
+
* @returns {Object} Route definition
|
|
168
|
+
*/
|
|
169
|
+
generateRouteDefinition(tableName, routeName, routeConfig, primaryKey, fileName, schema) {
|
|
170
|
+
const baseUrl = `/${fileName}`;
|
|
171
|
+
primaryKey = primaryKey || 'id';
|
|
172
|
+
|
|
173
|
+
const routeDefinitions = {
|
|
174
|
+
list: {
|
|
175
|
+
method: 'GET',
|
|
176
|
+
url: baseUrl,
|
|
177
|
+
schema: this.generateListSchema(tableName, routeConfig, primaryKey, schema)
|
|
178
|
+
},
|
|
179
|
+
get: {
|
|
180
|
+
method: 'GET',
|
|
181
|
+
url: `${baseUrl}/:${primaryKey}`,
|
|
182
|
+
schema: this.generateGetSchema(tableName, routeConfig, primaryKey, schema)
|
|
183
|
+
},
|
|
184
|
+
create: {
|
|
185
|
+
method: 'POST',
|
|
186
|
+
url: baseUrl,
|
|
187
|
+
schema: this.generateCreateSchema(tableName, routeConfig, primaryKey, schema)
|
|
188
|
+
},
|
|
189
|
+
update: {
|
|
190
|
+
method: 'PUT',
|
|
191
|
+
url: `${baseUrl}/:${primaryKey}`,
|
|
192
|
+
schema: this.generateUpdateSchema(tableName, routeConfig, primaryKey, schema)
|
|
193
|
+
},
|
|
194
|
+
delete: {
|
|
195
|
+
method: 'DELETE',
|
|
196
|
+
url: `${baseUrl}/:${primaryKey}`,
|
|
197
|
+
schema: this.generateDeleteSchema(tableName, routeConfig, primaryKey, schema)
|
|
198
|
+
},
|
|
199
|
+
upload: {
|
|
200
|
+
method: 'POST',
|
|
201
|
+
url: `${baseUrl}/:${primaryKey}/upload`,
|
|
202
|
+
schema: this.generateUploadSchema(tableName, routeConfig, primaryKey, schema),
|
|
203
|
+
validatorCompiler: ({ schema }) => {
|
|
204
|
+
return (data) => {
|
|
205
|
+
return true;
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
return routeDefinitions[routeName];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Generate list schema
|
|
216
|
+
* @param {string} tableName - Table name
|
|
217
|
+
* @param {Object} routeConfig - Route configuration
|
|
218
|
+
* @param {Object} schema - Schema object
|
|
219
|
+
* @param {string} primaryKey - Primary key
|
|
220
|
+
* @param {Object} filters - Filters object
|
|
221
|
+
* @returns {Object} Schema
|
|
222
|
+
*/
|
|
223
|
+
generateListSchema(tableName, routeConfig, primaryKey, schema) {
|
|
224
|
+
const itemSchema = this.generateItemSchema(schema, primaryKey);
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
description: `Get all ${tableName} with pagination and search`,
|
|
228
|
+
tags: [general.titleCase(tableName)],
|
|
229
|
+
summary: routeConfig.summary || `List of ${tableName} with pagination and search`,
|
|
230
|
+
querystring: {
|
|
231
|
+
type: 'object',
|
|
232
|
+
properties: {
|
|
233
|
+
page: { type: 'integer', minimum: 1, default: 1 },
|
|
234
|
+
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
|
235
|
+
search: { type: 'string' },
|
|
236
|
+
order: { type: 'string', default: `${primaryKey}:desc` },
|
|
237
|
+
...Object.entries(schema).reduce((acc, [key, value]) => {
|
|
238
|
+
if(value.search_type) {
|
|
239
|
+
acc[key] = { type: value.type, default: value.default || undefined, enum: value.enum || undefined };
|
|
240
|
+
}
|
|
241
|
+
return acc;
|
|
242
|
+
}, {})
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
headers: {
|
|
246
|
+
type: 'object',
|
|
247
|
+
properties: {
|
|
248
|
+
locale: { type: 'string', default: 'en' }
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
response: {
|
|
252
|
+
200: {
|
|
253
|
+
type: 'object',
|
|
254
|
+
properties: {
|
|
255
|
+
status: { type: 'boolean', default: true },
|
|
256
|
+
data: {
|
|
257
|
+
type: 'array',
|
|
258
|
+
items: itemSchema,
|
|
259
|
+
additionalProperties: false
|
|
260
|
+
},
|
|
261
|
+
pagination: {
|
|
262
|
+
type: 'object',
|
|
263
|
+
properties: {
|
|
264
|
+
page: { type: 'integer', example: 1 },
|
|
265
|
+
limit: { type: 'integer', example: 10 },
|
|
266
|
+
total: { type: 'integer', example: 100 },
|
|
267
|
+
totalPages: { type: 'integer', example: 10 }
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Generate get schema
|
|
278
|
+
* @param {string} tableName - Table name
|
|
279
|
+
* @param {Object} routeConfig - Route configuration
|
|
280
|
+
* @returns {Object} Schema
|
|
281
|
+
*/
|
|
282
|
+
generateGetSchema(tableName, routeConfig, primaryKey, schema) {
|
|
283
|
+
const itemSchema = this.generateItemSchema(schema, primaryKey);
|
|
284
|
+
primaryKey = primaryKey || 'id';
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
description: `Get ${tableName} by ${primaryKey}`,
|
|
288
|
+
tags: [general.titleCase(tableName)],
|
|
289
|
+
summary: routeConfig.summary || `Get ${tableName} by ${primaryKey}`,
|
|
290
|
+
params: {
|
|
291
|
+
type: 'object',
|
|
292
|
+
properties: {
|
|
293
|
+
[primaryKey]: { type: 'integer', minimum: 1 }
|
|
294
|
+
},
|
|
295
|
+
required: [primaryKey],
|
|
296
|
+
additionalProperties: false
|
|
297
|
+
},
|
|
298
|
+
response: {
|
|
299
|
+
200: {
|
|
300
|
+
type: 'object',
|
|
301
|
+
properties: {
|
|
302
|
+
status: { type: 'boolean', default: true },
|
|
303
|
+
data: itemSchema
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
404: {
|
|
307
|
+
type: 'object',
|
|
308
|
+
properties: {
|
|
309
|
+
status: { type: 'boolean', default: false },
|
|
310
|
+
message: { type: 'string' }
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Generate create schema
|
|
319
|
+
* @param {string} tableName - Table name
|
|
320
|
+
* @param {Object} routeConfig - Route configuration
|
|
321
|
+
* @returns {Object} Schema
|
|
322
|
+
*/
|
|
323
|
+
generateCreateSchema(tableName, routeConfig, primaryKey, schema) {
|
|
324
|
+
const bodySchema = this.generateBodySchema(schema, false, primaryKey);
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
description: `Create new ${tableName}`,
|
|
328
|
+
tags: [general.titleCase(tableName)],
|
|
329
|
+
summary: routeConfig.summary || `Create new ${tableName}`,
|
|
330
|
+
body: bodySchema,
|
|
331
|
+
response: {
|
|
332
|
+
200: {
|
|
333
|
+
type: 'object',
|
|
334
|
+
properties: {
|
|
335
|
+
status: { type: 'boolean', default: true },
|
|
336
|
+
data: this.generateItemSchema(schema, primaryKey)
|
|
337
|
+
},
|
|
338
|
+
additionalProperties: false
|
|
339
|
+
},
|
|
340
|
+
400: {
|
|
341
|
+
type: 'object',
|
|
342
|
+
properties: {
|
|
343
|
+
status: { type: 'boolean', default: false },
|
|
344
|
+
message: { type: 'string' }
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Generate update schema
|
|
353
|
+
* @param {string} tableName - Table name
|
|
354
|
+
* @param {Object} routeConfig - Route configuration
|
|
355
|
+
* @returns {Object} Schema
|
|
356
|
+
*/
|
|
357
|
+
generateUpdateSchema(tableName, routeConfig, primaryKey, schema) {
|
|
358
|
+
const bodySchema = this.generateBodySchema(schema, true, primaryKey, 'updatable');
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
description: `Update ${tableName} by ID`,
|
|
362
|
+
tags: [general.titleCase(tableName)],
|
|
363
|
+
summary: routeConfig.summary || `Update ${tableName} by ${primaryKey}`,
|
|
364
|
+
params: {
|
|
365
|
+
type: 'object',
|
|
366
|
+
properties: {
|
|
367
|
+
[primaryKey]: { type: 'integer', minimum: 1 }
|
|
368
|
+
},
|
|
369
|
+
required: [primaryKey],
|
|
370
|
+
additionalProperties: false
|
|
371
|
+
},
|
|
372
|
+
body: bodySchema,
|
|
373
|
+
response: {
|
|
374
|
+
200: {
|
|
375
|
+
type: 'object',
|
|
376
|
+
properties: {
|
|
377
|
+
status: { type: 'boolean', default: true },
|
|
378
|
+
data: this.generateItemSchema(schema, primaryKey)
|
|
379
|
+
},
|
|
380
|
+
additionalProperties: false
|
|
381
|
+
},
|
|
382
|
+
404: {
|
|
383
|
+
type: 'object',
|
|
384
|
+
properties: {
|
|
385
|
+
status: { type: 'boolean', default: false },
|
|
386
|
+
message: { type: 'string' }
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Generate delete schema
|
|
395
|
+
* @param {string} tableName - Table name
|
|
396
|
+
* @param {Object} routeConfig - Route configuration
|
|
397
|
+
* @returns {Object} Schema
|
|
398
|
+
*/
|
|
399
|
+
generateDeleteSchema(tableName, routeConfig, primaryKey, schema) {
|
|
400
|
+
return {
|
|
401
|
+
description: `Delete ${tableName} by ID`,
|
|
402
|
+
tags: [general.titleCase(tableName)],
|
|
403
|
+
summary: routeConfig.summary || `Delete ${tableName} by ${primaryKey}`,
|
|
404
|
+
params: {
|
|
405
|
+
type: 'object',
|
|
406
|
+
properties: {
|
|
407
|
+
[primaryKey]: { type: 'integer', minimum: 1 }
|
|
408
|
+
},
|
|
409
|
+
required: [primaryKey],
|
|
410
|
+
additionalProperties: false
|
|
411
|
+
},
|
|
412
|
+
response: {
|
|
413
|
+
200: {
|
|
414
|
+
type: 'object',
|
|
415
|
+
properties: {
|
|
416
|
+
status: { type: 'boolean', default: true },
|
|
417
|
+
data: this.generateItemSchema(schema, primaryKey)
|
|
418
|
+
},
|
|
419
|
+
additionalProperties: false
|
|
420
|
+
},
|
|
421
|
+
404: {
|
|
422
|
+
type: 'object',
|
|
423
|
+
properties: {
|
|
424
|
+
status: { type: 'boolean', default: false },
|
|
425
|
+
message: { type: 'string' }
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Generate upload schema
|
|
434
|
+
* @param {string} tableName - Table name
|
|
435
|
+
* @param {Object} routeConfig - Route configuration
|
|
436
|
+
* @returns {Object} Schema
|
|
437
|
+
*/
|
|
438
|
+
generateUploadSchema(tableName, routeConfig, primaryKey, schema) {
|
|
439
|
+
const uploadField = routeConfig.upload_field || 'file';
|
|
440
|
+
return {
|
|
441
|
+
description: `Upload file for ${tableName} by ${primaryKey}`,
|
|
442
|
+
tags: [general.titleCase(tableName)],
|
|
443
|
+
summary: routeConfig.summary || `Upload file for ${tableName} by ${primaryKey}`,
|
|
444
|
+
params: {
|
|
445
|
+
type: 'object',
|
|
446
|
+
properties: {
|
|
447
|
+
[primaryKey]: { type: 'integer', minimum: 1 }
|
|
448
|
+
},
|
|
449
|
+
required: [primaryKey],
|
|
450
|
+
additionalProperties: false
|
|
451
|
+
},
|
|
452
|
+
consumes: ['multipart/form-data'],
|
|
453
|
+
body: {
|
|
454
|
+
type: 'object',
|
|
455
|
+
properties: {
|
|
456
|
+
[uploadField]: { type: 'string', format: 'binary' }
|
|
457
|
+
},
|
|
458
|
+
additionalProperties: false
|
|
459
|
+
},
|
|
460
|
+
response: {
|
|
461
|
+
200: {
|
|
462
|
+
type: 'object',
|
|
463
|
+
properties: {
|
|
464
|
+
status: { type: 'boolean', default: true },
|
|
465
|
+
data: this.generateItemSchema(schema, primaryKey)
|
|
466
|
+
},
|
|
467
|
+
additionalProperties: false
|
|
468
|
+
},
|
|
469
|
+
404: {
|
|
470
|
+
type: 'object',
|
|
471
|
+
properties: {
|
|
472
|
+
status: { type: 'boolean', default: false },
|
|
473
|
+
message: { type: 'string' }
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Generate item schema for responses
|
|
482
|
+
* @param {Object} itemSchema - Item schema from config
|
|
483
|
+
* @returns {Object} Item schema
|
|
484
|
+
*/
|
|
485
|
+
generateItemSchema(schema, primaryKey) {
|
|
486
|
+
if (!schema) {
|
|
487
|
+
return { type: 'object' };
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const withoutReferences = { ...schema };
|
|
491
|
+
|
|
492
|
+
if(Object.entries(withoutReferences).length > 0) {
|
|
493
|
+
for(const [key, value] of Object.entries(withoutReferences)) {
|
|
494
|
+
const withoutReferencesValue = { ...value };
|
|
495
|
+
if(withoutReferencesValue.listable !== false) {
|
|
496
|
+
if(Object.entries(withoutReferencesValue).length > 0) {
|
|
497
|
+
for(const [key2,] of Object.entries(withoutReferencesValue)) {
|
|
498
|
+
if(this.deleteKeys.includes(key2)) {
|
|
499
|
+
delete withoutReferencesValue[key2];
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
withoutReferences[key] = value;
|
|
504
|
+
} else {
|
|
505
|
+
delete withoutReferences[key];
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return {
|
|
511
|
+
type: 'object',
|
|
512
|
+
properties: withoutReferences,
|
|
513
|
+
additionalProperties: false
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Generate body schema for create/update
|
|
519
|
+
* @param {Object} itemSchema - Item schema from config
|
|
520
|
+
* @param {boolean} isUpdate - Whether this is for update (optional fields)
|
|
521
|
+
* @returns {Object} Body schema
|
|
522
|
+
*/
|
|
523
|
+
generateBodySchema(schema, isUpdate = false, primaryKey = 'id', type = 'creatable') {
|
|
524
|
+
if (!schema) {
|
|
525
|
+
return { type: 'object' };
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Filter out auto-generated fields for create/update
|
|
529
|
+
const filteredSchema = { ...schema };
|
|
530
|
+
delete filteredSchema[primaryKey];
|
|
531
|
+
delete filteredSchema.created_at;
|
|
532
|
+
delete filteredSchema.updated_at;
|
|
533
|
+
|
|
534
|
+
const requiredKeys = Object.keys(filteredSchema).filter(key => type == 'creatable' ? filteredSchema[key].create_required : filteredSchema[key].updatable_required);
|
|
535
|
+
// delete required keys in objects
|
|
536
|
+
const props = Object.entries(filteredSchema).reduce((acc, [key, value]) => {
|
|
537
|
+
if (typeof value === 'object') {
|
|
538
|
+
const withoutReferences = Object.assign({}, value);
|
|
539
|
+
if(withoutReferences[type]) {
|
|
540
|
+
if(Object.entries(withoutReferences).length > 0) {
|
|
541
|
+
for(const [key2,] of Object.entries(withoutReferences)) {
|
|
542
|
+
if(this.deleteKeys.includes(key2)) {
|
|
543
|
+
delete withoutReferences[key2];
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if(type == 'updatable') {
|
|
549
|
+
delete withoutReferences.default
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
acc[key] = withoutReferences;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return acc;
|
|
556
|
+
}, {});
|
|
557
|
+
|
|
558
|
+
return {
|
|
559
|
+
type: 'object',
|
|
560
|
+
required: requiredKeys,
|
|
561
|
+
properties: props,
|
|
562
|
+
additionalProperties: false,
|
|
563
|
+
...(isUpdate ? { minProperties: 1 } : {})
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Get handler function for route type
|
|
569
|
+
* @param {string} routeName - Route name
|
|
570
|
+
* @returns {Function} Handler function
|
|
571
|
+
*/
|
|
572
|
+
getHandler(routeName) {
|
|
573
|
+
|
|
574
|
+
const handlerMap = {
|
|
575
|
+
'list': handlers.getAll,
|
|
576
|
+
'get': handlers.getById,
|
|
577
|
+
'create': handlers.create,
|
|
578
|
+
'update': handlers.update,
|
|
579
|
+
'delete': handlers.delete,
|
|
580
|
+
'upload': handlers.upload
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
return handlerMap[routeName];
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
module.exports = JsonRouteLoader;
|