@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,635 @@
|
|
|
1
|
+
const db = require('@root/libraries/knex');
|
|
2
|
+
const BCRYPT = require('@root/libraries/bcrypt');
|
|
3
|
+
const bcrypt = new BCRYPT();
|
|
4
|
+
const i18n = require('@root/utils/i18n');
|
|
5
|
+
const { uploadFile } = require('@root/libraries/upload');
|
|
6
|
+
const { randomChars } = require('@root/libraries/controls');
|
|
7
|
+
const { get_all, get_by_primary_key, create_item, update_item, delete_item } = require('@root/stores');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get all records with pagination, search, and join tables
|
|
11
|
+
* @param {Object} request - The request object
|
|
12
|
+
* @param {Object} reply - The reply object
|
|
13
|
+
* @param {Object} routeOptions - The route options
|
|
14
|
+
* @returns {Promise<Object>} - The response object
|
|
15
|
+
*/
|
|
16
|
+
async function getAll(request, reply, routeOptions = {}) {
|
|
17
|
+
|
|
18
|
+
if(routeOptions.store) {
|
|
19
|
+
return get_all(routeOptions.store, request.query, reply, routeOptions);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const { page = 1, limit = 10, search, order } = request.query;
|
|
24
|
+
const offset = (page - 1) * limit;
|
|
25
|
+
const tableName = routeOptions.tableName || 'users';
|
|
26
|
+
const routeConfig = routeOptions.routeConfig || {};
|
|
27
|
+
const primaryKey = routeOptions.primaryKey || 'id';
|
|
28
|
+
const filters = routeOptions.filters || {};
|
|
29
|
+
const splitOrder = order.split(':');
|
|
30
|
+
const orderBy = `${tableName}.${(splitOrder[0] || primaryKey)}`;
|
|
31
|
+
const orderDirection = splitOrder[1] || 'desc';
|
|
32
|
+
|
|
33
|
+
let query = db(tableName + ' as ' + tableName).select(`${tableName}.*`);
|
|
34
|
+
|
|
35
|
+
// Apply search filter if provided
|
|
36
|
+
if (search && routeConfig.searchable_fields) {
|
|
37
|
+
query = query.where(function() {
|
|
38
|
+
routeConfig.searchable_fields.forEach((field, index) => {
|
|
39
|
+
if (index === 0) {
|
|
40
|
+
this.where(tableName + '.' + field, 'like', `%${search}%`);
|
|
41
|
+
} else {
|
|
42
|
+
this.orWhere(tableName + '.' + field, 'like', `%${search}%`);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if(routeConfig.owned) {
|
|
49
|
+
query.where(routeConfig.owned, request.user?.id || 0);
|
|
50
|
+
query.where(routeConfig.owned_type, request.user?.type || 'user');
|
|
51
|
+
}
|
|
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];
|
|
57
|
+
if(joinTable.type == 'one-to-one') {
|
|
58
|
+
if(joinTable.extra_where) {
|
|
59
|
+
query.leftJoin(joinTable.table + ' as ' + tableKey, `${tableName}.${joinTable.foreign_key} = ${tableKey}.${joinTable.primary_key} AND ${joinTable.extra_where}`);
|
|
60
|
+
} else {
|
|
61
|
+
query.leftJoin(joinTable.table + ' as ' + tableKey, `${tableName}.${joinTable.foreign_key}`, `${tableKey}.${joinTable.primary_key}`);
|
|
62
|
+
}
|
|
63
|
+
query.select(`${tableKey}.*`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
query.options({ nestTables: true, rowMode: 'array' });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if(filters) {
|
|
71
|
+
for(let i = 0; i < Object.entries(filters).length; i++) {
|
|
72
|
+
const filter = Object.entries(filters)[i];
|
|
73
|
+
if(request.query[filter[0]] !== undefined) {
|
|
74
|
+
if(filter[1].search_type == 'like') {
|
|
75
|
+
query.where(tableName + '.' + filter[0], 'like', `%${request.query[filter[0]]}%`);
|
|
76
|
+
} else if(filter[1].search_type == 'equal') {
|
|
77
|
+
query.where(tableName + '.' + filter[0], request.query[filter[0]]);
|
|
78
|
+
} else if(filter[1].search_type == 'in') {
|
|
79
|
+
query.whereIn(tableName + '.' + filter[0], request.query[filter[0]]);
|
|
80
|
+
} else if(filter[1].search_type == 'not_in') {
|
|
81
|
+
query.whereNotIn(tableName + '.' + filter[0], request.query[filter[0]]);
|
|
82
|
+
} else if(filter[1].search_type == 'between') {
|
|
83
|
+
query.whereBetween(tableName + '.' + filter[0], request.query[filter[0]]);
|
|
84
|
+
} else if(filter[1].search_type == 'not_between') {
|
|
85
|
+
query.whereNotBetween(tableName + '.' + filter[0], request.query[filter[0]]);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Apply pagination
|
|
92
|
+
const items = await query
|
|
93
|
+
.orderBy(orderBy, orderDirection)
|
|
94
|
+
.limit(limit)
|
|
95
|
+
.offset(offset);
|
|
96
|
+
|
|
97
|
+
// Get total count for pagination
|
|
98
|
+
const totalQuery = query.clone();
|
|
99
|
+
totalQuery.clearSelect();
|
|
100
|
+
totalQuery.count(`${tableName}.${primaryKey} as count`);
|
|
101
|
+
totalQuery.options({ nestTables: false });
|
|
102
|
+
totalQuery.clearOrder();
|
|
103
|
+
totalQuery.clear('limit');
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
const [{ count }] = await totalQuery;
|
|
107
|
+
|
|
108
|
+
const total = parseInt(count);
|
|
109
|
+
|
|
110
|
+
const rows = structureRows(items, tableName, routeConfig.join_tables)
|
|
111
|
+
|
|
112
|
+
// Process join tables if configured
|
|
113
|
+
const processedItems = await processJoinTables(rows, routeConfig.join_tables);
|
|
114
|
+
|
|
115
|
+
// console.log(processedItems);
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
const totalPages = Math.ceil(total / limit);
|
|
119
|
+
|
|
120
|
+
return reply.send({
|
|
121
|
+
status: true,
|
|
122
|
+
data: processedItems,
|
|
123
|
+
pagination: {
|
|
124
|
+
page: parseInt(page),
|
|
125
|
+
limit: parseInt(limit),
|
|
126
|
+
total,
|
|
127
|
+
totalPages
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error('Error getting records:', error);
|
|
132
|
+
return reply.status(500).send({
|
|
133
|
+
status: false,
|
|
134
|
+
message: request.t('messages.internal_server_error')
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get record by ID with join tables
|
|
141
|
+
* @param {Object} request - The request object
|
|
142
|
+
* @param {Object} reply - The reply object
|
|
143
|
+
* @param {Object} routeOptions - The route options
|
|
144
|
+
* @returns {Promise<Object>} - The response object
|
|
145
|
+
*/
|
|
146
|
+
async function getById(request, reply, routeOptions = {}) {
|
|
147
|
+
|
|
148
|
+
if(routeOptions.store) {
|
|
149
|
+
return get_by_primary_key(routeOptions.store, request.params[routeOptions.primaryKey], reply, routeOptions);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const primaryKey = routeOptions.primaryKey || 'id';
|
|
154
|
+
const { [primaryKey]: id } = request.params;
|
|
155
|
+
const tableName = routeOptions.tableName || 'users';
|
|
156
|
+
const routeConfig = routeOptions.routeConfig || {};
|
|
157
|
+
|
|
158
|
+
const item_query = db(tableName)
|
|
159
|
+
.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];
|
|
166
|
+
if(joinTable.type == 'one-to-one') {
|
|
167
|
+
if(joinTable.extra_where) {
|
|
168
|
+
item_query.leftJoin(joinTable.table + ' as ' + tableKey, `${tableName}.${joinTable.foreign_key} = ${tableKey}.${joinTable.primary_key} AND ${joinTable.extra_where}`);
|
|
169
|
+
} else {
|
|
170
|
+
item_query.leftJoin(joinTable.table + ' as ' + tableKey, `${tableName}.${joinTable.foreign_key}`, `${tableKey}.${joinTable.primary_key}`);
|
|
171
|
+
}
|
|
172
|
+
item_query.select(`${tableKey}.*`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
item_query.options({ nestTables: true, rowMode: 'array' });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const item_data = await item_query.first();
|
|
180
|
+
console.log(item_data);
|
|
181
|
+
|
|
182
|
+
let item = {}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
if(routeConfig.join_tables) {
|
|
186
|
+
item = {
|
|
187
|
+
...item_data[tableName],
|
|
188
|
+
}
|
|
189
|
+
const join_one_to_one = Object.entries(routeConfig.join_tables).filter(n => n[1].type === 'one-to-one');
|
|
190
|
+
if(join_one_to_one.length > 0) {
|
|
191
|
+
for(let i = 0; i < join_one_to_one.length; i++) {
|
|
192
|
+
const join_table = join_one_to_one[i];
|
|
193
|
+
item[join_table[0]] = item_data[join_table[0]];
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
item = item_data;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (!item) {
|
|
201
|
+
return reply.status(404).send({
|
|
202
|
+
status: false,
|
|
203
|
+
message: request.t('messages.record_not_found')
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Process join tables if configured
|
|
208
|
+
const processedItem = await processJoinTables([item], routeConfig.join_tables);
|
|
209
|
+
const result = processedItem[0];
|
|
210
|
+
|
|
211
|
+
return reply.send({
|
|
212
|
+
status: true,
|
|
213
|
+
data: result
|
|
214
|
+
});
|
|
215
|
+
} catch (error) {
|
|
216
|
+
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
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Create new record
|
|
226
|
+
* @param {Object} request - The request object
|
|
227
|
+
* @param {Object} reply - The reply object
|
|
228
|
+
* @param {Object} routeOptions - The route options
|
|
229
|
+
* @returns {Promise<Object>} - The response object
|
|
230
|
+
*/
|
|
231
|
+
async function create(request, reply, routeOptions = {}) {
|
|
232
|
+
|
|
233
|
+
if(routeOptions.store) {
|
|
234
|
+
return create_item(routeOptions.store, request.body, reply, routeOptions);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
const tableName = routeOptions.tableName || 'users';
|
|
239
|
+
const primaryKey = routeOptions.primaryKey || 'id';
|
|
240
|
+
const routeConfig = routeOptions.routeConfig || {};
|
|
241
|
+
const schema = routeOptions.schema || {};
|
|
242
|
+
|
|
243
|
+
// Use payload field if available, otherwise use request.body directly
|
|
244
|
+
let data = request.body;
|
|
245
|
+
|
|
246
|
+
if (schema) {
|
|
247
|
+
const payloadFields = Object.entries(schema).filter(n => n[1].creatable || n[1].generatable);
|
|
248
|
+
data = {};
|
|
249
|
+
|
|
250
|
+
for (const field of payloadFields) {
|
|
251
|
+
if (request.body[field[0]] !== undefined || schema[field[0]].generatable) {
|
|
252
|
+
if (schema[field[0]].crypted) {
|
|
253
|
+
data[field[0]] = await bcrypt.hash(request.body[field[0]]);
|
|
254
|
+
} else if(schema[field[0]].generatable) {
|
|
255
|
+
data[field[0]] = await generateRandomString(typeof schema[field[0]].generatable === 'string' ? request.body[schema[field[0]].generatable] : null, field[0], tableName);
|
|
256
|
+
} else {
|
|
257
|
+
data[field[0]] = request.body[field[0]];
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
const [id] = await db(tableName)
|
|
265
|
+
.insert({
|
|
266
|
+
...data,
|
|
267
|
+
created_at: new Date(),
|
|
268
|
+
updated_at: new Date()
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const item = await db(tableName)
|
|
272
|
+
.where(primaryKey, id)
|
|
273
|
+
.first();
|
|
274
|
+
|
|
275
|
+
return reply.send({
|
|
276
|
+
status: true,
|
|
277
|
+
data: item
|
|
278
|
+
});
|
|
279
|
+
} catch (error) {
|
|
280
|
+
if(process.env.NODE_ENV === 'development') {
|
|
281
|
+
console.log(error.message);
|
|
282
|
+
}
|
|
283
|
+
if(error.sqlMessage) {
|
|
284
|
+
return sqlErrorHandler(error, request, reply);
|
|
285
|
+
} else {
|
|
286
|
+
return reply.status(500).send({
|
|
287
|
+
status: false,
|
|
288
|
+
message: request.t('messages.internal_server_error')
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
} catch (error) {
|
|
293
|
+
console.error('Error creating record:', error);
|
|
294
|
+
return reply.status(500).send({
|
|
295
|
+
status: false,
|
|
296
|
+
message: request.t('messages.internal_server_error')
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Update record by ID
|
|
303
|
+
* @param {Object} request - The request object
|
|
304
|
+
* @param {Object} reply - The reply object
|
|
305
|
+
* @param {Object} routeOptions - The route options
|
|
306
|
+
* @returns {Promise<Object>} - The response object
|
|
307
|
+
*/
|
|
308
|
+
async function update(request, reply, routeOptions = {}) {
|
|
309
|
+
|
|
310
|
+
if(routeOptions.store) {
|
|
311
|
+
return update_item(routeOptions.store, request.params[routeOptions.primaryKey], request.body, reply, routeOptions);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
const tableName = routeOptions.tableName || 'users';
|
|
316
|
+
const primaryKey = routeOptions.primaryKey || 'id';
|
|
317
|
+
const routeConfig = routeOptions.routeConfig || {};
|
|
318
|
+
const schema = routeOptions.schema || {};
|
|
319
|
+
|
|
320
|
+
// Use payload field if available, otherwise use request.body directly
|
|
321
|
+
let data = request.body;
|
|
322
|
+
if (schema) {
|
|
323
|
+
const payloadFields = Object.entries(schema).filter(n => n[1].updatable || n[1].generatable);
|
|
324
|
+
data = {};
|
|
325
|
+
|
|
326
|
+
for (const field of payloadFields) {
|
|
327
|
+
if (request.body[field[0]] !== undefined) {
|
|
328
|
+
if (schema[field[0]].crypted) {
|
|
329
|
+
data[field[0]] = await bcrypt.hash(request.body[field[0]]);
|
|
330
|
+
} else if(schema[field[0]].unique) {
|
|
331
|
+
const check_unique = await checkUnique(request.body[field[0]], field[0], tableName);
|
|
332
|
+
if(check_unique) {
|
|
333
|
+
return reply.status(400).send({
|
|
334
|
+
status: false,
|
|
335
|
+
message: request.t('messages.unique_constraint_violation')
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
data[field[0]] = request.body[field[0]];
|
|
339
|
+
}else {
|
|
340
|
+
data[field[0]] = request.body[field[0]];
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
// Check if record exists
|
|
348
|
+
const existingItem = await db(tableName)
|
|
349
|
+
.where(request.params)
|
|
350
|
+
.first();
|
|
351
|
+
|
|
352
|
+
if (!existingItem) {
|
|
353
|
+
return reply.status(404).send({
|
|
354
|
+
status: false,
|
|
355
|
+
message: request.t('messages.record_not_found')
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if(routeConfig.via && Object.entries(routeConfig.via).length > 0) {
|
|
360
|
+
for(let i = 0; i < Object.entries(routeConfig.via).length; i++) {
|
|
361
|
+
const via = Object.entries(routeConfig.via)[i];
|
|
362
|
+
data[via[0]] = via[1];
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
await db(tableName)
|
|
367
|
+
.where(request.params)
|
|
368
|
+
.update({
|
|
369
|
+
...data,
|
|
370
|
+
updated_at: new Date()
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
const updatedItem = await db(tableName)
|
|
374
|
+
.where(request.params)
|
|
375
|
+
.first();
|
|
376
|
+
|
|
377
|
+
return reply.send({
|
|
378
|
+
status: true,
|
|
379
|
+
data: updatedItem
|
|
380
|
+
});
|
|
381
|
+
} catch (error) {
|
|
382
|
+
console.error('Error updating record:', error);
|
|
383
|
+
return reply.status(500).send({
|
|
384
|
+
status: false,
|
|
385
|
+
message: request.t('messages.internal_server_error')
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Upload file for record by ID
|
|
392
|
+
* @param {Object} request - The request object
|
|
393
|
+
* @param {Object} reply - The reply object
|
|
394
|
+
* @param {Object} routeOptions - The route options
|
|
395
|
+
* @returns {Promise<Object>} - The response object
|
|
396
|
+
*/
|
|
397
|
+
async function upload(request, reply, routeOptions = {}) {
|
|
398
|
+
|
|
399
|
+
if(routeOptions.store) {
|
|
400
|
+
return upload_item(routeOptions.store, request.params[routeOptions.primaryKey], request.body[routeOptions.upload_field], reply, routeOptions);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
try {
|
|
404
|
+
const tableName = routeOptions.tableName || 'users';
|
|
405
|
+
const primaryKey = routeOptions.primaryKey || 'id';
|
|
406
|
+
const { [primaryKey]: id } = request.params;
|
|
407
|
+
const routeConfig = routeOptions.routeConfig || {};
|
|
408
|
+
|
|
409
|
+
const existingItem = await db(tableName)
|
|
410
|
+
.where(primaryKey, id)
|
|
411
|
+
.first();
|
|
412
|
+
|
|
413
|
+
if (!existingItem) {
|
|
414
|
+
return reply.status(404).send({
|
|
415
|
+
status: false,
|
|
416
|
+
message: request.t('messages.record_not_found')
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const uploadResult = await uploadFile(request.body[routeConfig.upload_field], id, tableName);
|
|
421
|
+
if(!uploadResult.status) {
|
|
422
|
+
return reply.status(500).send({
|
|
423
|
+
status: false,
|
|
424
|
+
message: request.t('messages.internal_server_error')
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
await db(tableName)
|
|
429
|
+
.where(primaryKey, id)
|
|
430
|
+
.update({
|
|
431
|
+
[routeConfig.upload_field]: uploadResult.file.fileUrl
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
return reply.send({
|
|
435
|
+
status: true,
|
|
436
|
+
data: {
|
|
437
|
+
[routeConfig.upload_field]: uploadResult.file.fileUrl
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
} catch (error) {
|
|
441
|
+
console.error('Error uploading file:', error);
|
|
442
|
+
return reply.status(500).send({
|
|
443
|
+
status: false,
|
|
444
|
+
message: request.t('messages.internal_server_error')
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Delete record by ID
|
|
451
|
+
* @param {Object} request - The request object
|
|
452
|
+
* @param {Object} reply - The reply object
|
|
453
|
+
* @param {Object} routeOptions - The route options
|
|
454
|
+
* @returns {Promise<Object>} - The response object
|
|
455
|
+
*/
|
|
456
|
+
async function deleteRecord(request, reply, routeOptions = {}) {
|
|
457
|
+
|
|
458
|
+
if(routeOptions.store) {
|
|
459
|
+
return delete_item(routeOptions.store, request.params[routeOptions.primaryKey], reply, routeOptions);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
try {
|
|
463
|
+
const { id } = request.params;
|
|
464
|
+
const tableName = routeOptions.tableName || 'users';
|
|
465
|
+
|
|
466
|
+
// Check if record exists
|
|
467
|
+
const existingItem = await db(tableName)
|
|
468
|
+
.where('id', id)
|
|
469
|
+
.first();
|
|
470
|
+
|
|
471
|
+
if (!existingItem) {
|
|
472
|
+
return reply.status(404).send({
|
|
473
|
+
status: false,
|
|
474
|
+
message: request.t('messages.record_not_found')
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
await db(tableName)
|
|
479
|
+
.where('id', id)
|
|
480
|
+
.del();
|
|
481
|
+
|
|
482
|
+
return reply.send({
|
|
483
|
+
status: true,
|
|
484
|
+
data: existingItem
|
|
485
|
+
});
|
|
486
|
+
} catch (error) {
|
|
487
|
+
console.error('Error deleting record:', error);
|
|
488
|
+
return reply.status(500).send({
|
|
489
|
+
status: false,
|
|
490
|
+
message: request.t('messages.internal_server_error')
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Generate random string
|
|
497
|
+
* @param {string} from_string - The string to generate random string from
|
|
498
|
+
* @param {string} field_name - The field name to check unique
|
|
499
|
+
* @param {string} tableName - The table name to check unique
|
|
500
|
+
* @returns {Promise<string>} - The random string
|
|
501
|
+
*/
|
|
502
|
+
const generateRandomString = async (from_string = null, field_name = null, tableName = null) => {
|
|
503
|
+
let random_string = '';
|
|
504
|
+
|
|
505
|
+
if(from_string.includes('@')) {
|
|
506
|
+
random_string = from_string.split('@')[0];
|
|
507
|
+
} else {
|
|
508
|
+
random_string = randomChars(10);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const existing_item = await checkUnique(random_string, field_name, tableName);
|
|
512
|
+
|
|
513
|
+
if(existing_item) {
|
|
514
|
+
return generateRandomString(from_string, field_name, tableName);
|
|
515
|
+
}
|
|
516
|
+
return random_string;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Check if the value is unique
|
|
521
|
+
* @param {string} value - The value to check unique
|
|
522
|
+
* @param {string} field_name - The field name to check unique
|
|
523
|
+
* @param {string} tableName - The table name to check unique
|
|
524
|
+
* @returns {Promise<boolean>} - The unique status
|
|
525
|
+
*/
|
|
526
|
+
const checkUnique = async (value = null, field_name = null, tableName = null) => {
|
|
527
|
+
const existing_item = await db(tableName).select(field_name).where(field_name, value).first();
|
|
528
|
+
return existing_item ? true : false;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Handle SQL error
|
|
533
|
+
* @param {Object} error - The error object
|
|
534
|
+
* @param {Object} request - The request object
|
|
535
|
+
* @param {Object} reply - The reply object
|
|
536
|
+
* @returns {Object} - The response object
|
|
537
|
+
*/
|
|
538
|
+
const sqlErrorHandler = (error, request, reply) => {
|
|
539
|
+
return reply.status(500).send({
|
|
540
|
+
status: false,
|
|
541
|
+
message: i18n.t('sql.' + error.code.toLowerCase(), request.headers.locale || 'en', {...request.body, ...request.query, ...request.params})
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Structure rows
|
|
547
|
+
* @param {Array} items - The items to structure
|
|
548
|
+
* @param {string} tableName - The table name to structure
|
|
549
|
+
* @param {Object} joinTables - The join tables to structure
|
|
550
|
+
* @returns {Array} - The structured rows
|
|
551
|
+
*/
|
|
552
|
+
const structureRows = (items, tableName, joinTables) => {
|
|
553
|
+
|
|
554
|
+
if(!joinTables) {
|
|
555
|
+
return items;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
return items.map(item => {
|
|
559
|
+
const other_items = Object.entries(item).filter(n => n[0] !== tableName).reduce((acc, curr) => {
|
|
560
|
+
// check if all items null then return null
|
|
561
|
+
if(Object.values(curr[1]).every(n => n === null)) {
|
|
562
|
+
acc[curr[0]] = null;
|
|
563
|
+
} else {
|
|
564
|
+
acc[curr[0]] = curr[1];
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return acc;
|
|
568
|
+
}, {});
|
|
569
|
+
|
|
570
|
+
return {
|
|
571
|
+
...item[tableName],
|
|
572
|
+
...other_items
|
|
573
|
+
}
|
|
574
|
+
})
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Process join tables
|
|
579
|
+
* @param {Array} items - The items to process join tables
|
|
580
|
+
* @param {Object} joinTables - The join tables to process
|
|
581
|
+
* @returns {Array} - The processed items
|
|
582
|
+
*/
|
|
583
|
+
const processJoinTables = async (items, joinTables) => {
|
|
584
|
+
|
|
585
|
+
if(!joinTables) {
|
|
586
|
+
return items;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const length_of_one_to_many = Object.entries(joinTables).filter(n => n[1].type === 'one-to-many');
|
|
590
|
+
if(length_of_one_to_many.length > 0) {
|
|
591
|
+
for(let i = 0; i < length_of_one_to_many.length; i++) {
|
|
592
|
+
const joinTable = length_of_one_to_many[i][1];
|
|
593
|
+
const joinTableKey = length_of_one_to_many[i][0];
|
|
594
|
+
|
|
595
|
+
const personal_ids = items.map(item => item[joinTable.primary_key]);
|
|
596
|
+
|
|
597
|
+
const personal_items = db(joinTable.table).whereIn(joinTable.table + '.' + joinTable.foreign_key, personal_ids);
|
|
598
|
+
|
|
599
|
+
if(joinTable.extra_where) {
|
|
600
|
+
personal_items.where(db.raw(joinTable.table + '.' + joinTable.extra_where));
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if(joinTable.join_table) {
|
|
604
|
+
personal_items.join(joinTable.join_table, joinTable.table + '.' + joinTable.join_key, '=', joinTable.join_table + '.' + joinTable.join_value);
|
|
605
|
+
personal_items.options({ nestTables: true, rowMode: 'array' });
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const personal_items_data = await personal_items;
|
|
609
|
+
|
|
610
|
+
if(joinTable.join_table) {
|
|
611
|
+
items.forEach(item => {
|
|
612
|
+
item[joinTableKey] = personal_items_data.filter(n => n[joinTable.table][joinTable.foreign_key] === item[joinTable.primary_key]);
|
|
613
|
+
});
|
|
614
|
+
} else {
|
|
615
|
+
items.forEach(item => {
|
|
616
|
+
item[joinTableKey] = personal_items_data.filter(n => n[joinTable.foreign_key] === item[joinTable.primary_key]);
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return items
|
|
622
|
+
} else {
|
|
623
|
+
return items;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
module.exports = {
|
|
628
|
+
getAll,
|
|
629
|
+
getById,
|
|
630
|
+
create,
|
|
631
|
+
update,
|
|
632
|
+
delete: deleteRecord,
|
|
633
|
+
upload,
|
|
634
|
+
processJoinTables
|
|
635
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"table": "countries",
|
|
3
|
+
"primary_key": "country_id",
|
|
4
|
+
"schema": {
|
|
5
|
+
"country_id": { "type": "integer", "example": 1 },
|
|
6
|
+
"name": { "type": "string", "default": "", "example": "United States" },
|
|
7
|
+
"code": { "type": "string", "default": "", "search_type": "equal", "example": "US" },
|
|
8
|
+
"iso_code": { "type": "string", "default": "", "search_type": "equal", "example": "USA" },
|
|
9
|
+
"phone_code": { "type": "string", "default": "", "example": "+1" },
|
|
10
|
+
"flag": { "type": "string", "default": "", "example": "🇺🇸" }
|
|
11
|
+
},
|
|
12
|
+
"store": "countries",
|
|
13
|
+
"routes": [
|
|
14
|
+
{
|
|
15
|
+
"action": "list",
|
|
16
|
+
"method": "GET",
|
|
17
|
+
"path": "/countries",
|
|
18
|
+
"searchable_fields": ["name", "code", "iso_code", "phone_code", "flag"],
|
|
19
|
+
"pagination": true,
|
|
20
|
+
"additionalProperties": false
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"action": "get",
|
|
24
|
+
"method": "GET",
|
|
25
|
+
"path": "/countries/:country_id"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"table": "currencies",
|
|
3
|
+
"primary_key": "currency_id",
|
|
4
|
+
"store": "currencies",
|
|
5
|
+
"schema": {
|
|
6
|
+
"currency_id": { "type": "integer", "example": 1 },
|
|
7
|
+
"code": { "type": "string", "default": "", "example": "USD" },
|
|
8
|
+
"symbol": { "type": "string", "default": "", "example": "$" },
|
|
9
|
+
"name": { "type": "string", "default": "", "example": "United States Dollar" }
|
|
10
|
+
},
|
|
11
|
+
"routes": [
|
|
12
|
+
{
|
|
13
|
+
"action": "list",
|
|
14
|
+
"method": "GET",
|
|
15
|
+
"path": "/currencies",
|
|
16
|
+
"searchable_fields": ["code", "name"],
|
|
17
|
+
"pagination": true,
|
|
18
|
+
"additionalProperties": false
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"action": "get",
|
|
22
|
+
"method": "GET",
|
|
23
|
+
"path": "/currencies/:currency_id"
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|