webspresso 0.0.37 → 0.0.38
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/package.json
CHANGED
|
@@ -68,6 +68,9 @@ const state = {
|
|
|
68
68
|
filters: {}, // Active filters { column: { op, value, from, to } }
|
|
69
69
|
filterPanelOpen: false, // Filter panel visibility (deprecated)
|
|
70
70
|
filterDrawerOpen: false, // Filter drawer visibility
|
|
71
|
+
bulkFields: [], // Bulk-updatable fields (enum/boolean)
|
|
72
|
+
bulkFieldDropdownOpen: false, // Bulk field dropdown visibility
|
|
73
|
+
selectedBulkField: null, // Currently selected bulk field for update
|
|
71
74
|
};
|
|
72
75
|
|
|
73
76
|
// Breadcrumb Component
|
|
@@ -1303,6 +1306,152 @@ function formatCellValue(value, col) {
|
|
|
1303
1306
|
}
|
|
1304
1307
|
}
|
|
1305
1308
|
|
|
1309
|
+
// Load bulk-updatable fields for a model
|
|
1310
|
+
async function loadBulkFields(modelName) {
|
|
1311
|
+
try {
|
|
1312
|
+
const response = await api.get('/extensions/bulk-fields/' + modelName);
|
|
1313
|
+
state.bulkFields = response.fields || [];
|
|
1314
|
+
m.redraw();
|
|
1315
|
+
} catch (err) {
|
|
1316
|
+
console.error('Failed to load bulk fields:', err);
|
|
1317
|
+
state.bulkFields = [];
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
// Execute bulk field update
|
|
1322
|
+
async function executeBulkFieldUpdate(modelName, field, value, ids) {
|
|
1323
|
+
try {
|
|
1324
|
+
const response = await api.post('/extensions/bulk-update/' + modelName, {
|
|
1325
|
+
ids: ids,
|
|
1326
|
+
field: field,
|
|
1327
|
+
value: value,
|
|
1328
|
+
});
|
|
1329
|
+
return response;
|
|
1330
|
+
} catch (err) {
|
|
1331
|
+
throw err;
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
// Bulk Field Update Dropdown Component
|
|
1336
|
+
const BulkFieldUpdateDropdown = {
|
|
1337
|
+
view: (vnode) => {
|
|
1338
|
+
const { modelName, selectedIds, onComplete } = vnode.attrs;
|
|
1339
|
+
|
|
1340
|
+
if (!state.bulkFields || state.bulkFields.length === 0) {
|
|
1341
|
+
return null;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
return m('.relative.inline-block', [
|
|
1345
|
+
// Dropdown trigger
|
|
1346
|
+
m('button.inline-flex.items-center.gap-1.px-3.py-1.5.text-sm.font-medium.text-purple-600.bg-white.border.border-purple-200.rounded.hover:bg-purple-50.transition-colors', {
|
|
1347
|
+
disabled: state.bulkActionInProgress,
|
|
1348
|
+
onclick: (e) => {
|
|
1349
|
+
e.stopPropagation();
|
|
1350
|
+
state.bulkFieldDropdownOpen = !state.bulkFieldDropdownOpen;
|
|
1351
|
+
state.selectedBulkField = null;
|
|
1352
|
+
m.redraw();
|
|
1353
|
+
},
|
|
1354
|
+
}, [
|
|
1355
|
+
m('svg.w-4.h-4', { fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor' },
|
|
1356
|
+
m('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', d: 'M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z' })
|
|
1357
|
+
),
|
|
1358
|
+
'Set Field',
|
|
1359
|
+
m('svg.w-4.h-4.ml-1', { fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor' },
|
|
1360
|
+
m('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', d: 'M19 9l-7 7-7-7' })
|
|
1361
|
+
),
|
|
1362
|
+
]),
|
|
1363
|
+
|
|
1364
|
+
// Dropdown menu
|
|
1365
|
+
state.bulkFieldDropdownOpen && m('.absolute.z-50.mt-1.w-64.bg-white.rounded-lg.shadow-lg.border.border-gray-200.overflow-hidden', {
|
|
1366
|
+
style: 'left: 0; top: 100%;',
|
|
1367
|
+
onclick: (e) => e.stopPropagation(),
|
|
1368
|
+
}, [
|
|
1369
|
+
// Close button area click handler
|
|
1370
|
+
m('.fixed.inset-0.z-40', {
|
|
1371
|
+
onclick: () => {
|
|
1372
|
+
state.bulkFieldDropdownOpen = false;
|
|
1373
|
+
state.selectedBulkField = null;
|
|
1374
|
+
m.redraw();
|
|
1375
|
+
},
|
|
1376
|
+
}),
|
|
1377
|
+
|
|
1378
|
+
// Dropdown content
|
|
1379
|
+
m('.relative.z-50.bg-white', [
|
|
1380
|
+
// Header
|
|
1381
|
+
m('.px-3.py-2.bg-gray-50.border-b.border-gray-200', [
|
|
1382
|
+
m('span.text-xs.font-medium.text-gray-500.uppercase.tracking-wider',
|
|
1383
|
+
state.selectedBulkField ? 'Select Value' : 'Select Field'
|
|
1384
|
+
),
|
|
1385
|
+
]),
|
|
1386
|
+
|
|
1387
|
+
// Field list or value list
|
|
1388
|
+
m('.max-h-64.overflow-y-auto', [
|
|
1389
|
+
state.selectedBulkField
|
|
1390
|
+
// Show values for selected field
|
|
1391
|
+
? state.selectedBulkField.options.map(option =>
|
|
1392
|
+
m('button.w-full.px-3.py-2.text-left.text-sm.hover:bg-purple-50.flex.items-center.justify-between.transition-colors', {
|
|
1393
|
+
onclick: async () => {
|
|
1394
|
+
state.bulkActionInProgress = true;
|
|
1395
|
+
state.bulkFieldDropdownOpen = false;
|
|
1396
|
+
m.redraw();
|
|
1397
|
+
|
|
1398
|
+
try {
|
|
1399
|
+
await executeBulkFieldUpdate(modelName, state.selectedBulkField.name, option.value, selectedIds);
|
|
1400
|
+
state.selectedBulkField = null;
|
|
1401
|
+
if (onComplete) onComplete();
|
|
1402
|
+
} catch (err) {
|
|
1403
|
+
alert('Error: ' + err.message);
|
|
1404
|
+
} finally {
|
|
1405
|
+
state.bulkActionInProgress = false;
|
|
1406
|
+
m.redraw();
|
|
1407
|
+
}
|
|
1408
|
+
},
|
|
1409
|
+
}, [
|
|
1410
|
+
m('span.text-gray-700', String(option.label)),
|
|
1411
|
+
state.selectedBulkField.type === 'boolean' && m('span.ml-2',
|
|
1412
|
+
option.value === true
|
|
1413
|
+
? m('span.inline-flex.items-center.px-2.py-0.5.rounded-full.text-xs.font-medium.bg-green-100.text-green-800', '✓')
|
|
1414
|
+
: m('span.inline-flex.items-center.px-2.py-0.5.rounded-full.text-xs.font-medium.bg-gray-100.text-gray-600', '✗')
|
|
1415
|
+
),
|
|
1416
|
+
])
|
|
1417
|
+
)
|
|
1418
|
+
// Show field list
|
|
1419
|
+
: state.bulkFields.map(field =>
|
|
1420
|
+
m('button.w-full.px-3.py-2.text-left.text-sm.hover:bg-purple-50.flex.items-center.justify-between.transition-colors', {
|
|
1421
|
+
onclick: () => {
|
|
1422
|
+
state.selectedBulkField = field;
|
|
1423
|
+
m.redraw();
|
|
1424
|
+
},
|
|
1425
|
+
}, [
|
|
1426
|
+
m('.flex.items-center.gap-2', [
|
|
1427
|
+
m('span.text-gray-700', formatColumnLabel(field.label || field.name)),
|
|
1428
|
+
m('span.text-xs.text-gray-400.uppercase', field.type),
|
|
1429
|
+
]),
|
|
1430
|
+
m('svg.w-4.h-4.text-gray-400', { fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor' },
|
|
1431
|
+
m('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', d: 'M9 5l7 7-7 7' })
|
|
1432
|
+
),
|
|
1433
|
+
])
|
|
1434
|
+
),
|
|
1435
|
+
|
|
1436
|
+
// Back button when viewing values
|
|
1437
|
+
state.selectedBulkField && m('button.w-full.px-3.py-2.text-left.text-sm.text-gray-500.hover:bg-gray-50.border-t.border-gray-100.flex.items-center.gap-1', {
|
|
1438
|
+
onclick: () => {
|
|
1439
|
+
state.selectedBulkField = null;
|
|
1440
|
+
m.redraw();
|
|
1441
|
+
},
|
|
1442
|
+
}, [
|
|
1443
|
+
m('svg.w-4.h-4', { fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor' },
|
|
1444
|
+
m('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', d: 'M15 19l-7-7 7-7' })
|
|
1445
|
+
),
|
|
1446
|
+
'Back to fields',
|
|
1447
|
+
]),
|
|
1448
|
+
]),
|
|
1449
|
+
]),
|
|
1450
|
+
]),
|
|
1451
|
+
]);
|
|
1452
|
+
},
|
|
1453
|
+
};
|
|
1454
|
+
|
|
1306
1455
|
// Get columns to display in table (limit to reasonable number)
|
|
1307
1456
|
function getDisplayColumns(columns) {
|
|
1308
1457
|
if (!columns || columns.length === 0) return [];
|
|
@@ -1463,6 +1612,9 @@ function initializeModelView(modelName) {
|
|
|
1463
1612
|
state.filterDrawerOpen = false;
|
|
1464
1613
|
state.selectedRecords = new Set(); // Bulk selection
|
|
1465
1614
|
state.bulkActionInProgress = false;
|
|
1615
|
+
state.bulkFields = []; // Reset bulk fields
|
|
1616
|
+
state.bulkFieldDropdownOpen = false;
|
|
1617
|
+
state.selectedBulkField = null;
|
|
1466
1618
|
state._currentModelName = modelName;
|
|
1467
1619
|
|
|
1468
1620
|
// Parse filters from URL query string
|
|
@@ -1476,6 +1628,8 @@ function initializeModelView(modelName) {
|
|
|
1476
1628
|
.then(modelMeta => {
|
|
1477
1629
|
state.currentModelMeta = modelMeta;
|
|
1478
1630
|
state.currentModel = modelMeta;
|
|
1631
|
+
// Load bulk-updatable fields for this model
|
|
1632
|
+
loadBulkFields(modelName);
|
|
1479
1633
|
return loadRecords(modelName, page, state.filters);
|
|
1480
1634
|
})
|
|
1481
1635
|
.catch(err => {
|
|
@@ -1631,7 +1785,7 @@ const RecordList = {
|
|
|
1631
1785
|
m.redraw();
|
|
1632
1786
|
try {
|
|
1633
1787
|
const ids = Array.from(state.selectedRecords);
|
|
1634
|
-
await api.post('/extensions/bulk-actions/bulk-delete/
|
|
1788
|
+
await api.post('/extensions/bulk-actions/bulk-delete/' + modelName, { ids });
|
|
1635
1789
|
state.selectedRecords = new Set();
|
|
1636
1790
|
loadRecords(modelName, state.pagination.page);
|
|
1637
1791
|
} catch (err) {
|
|
@@ -1705,9 +1859,20 @@ const RecordList = {
|
|
|
1705
1859
|
),
|
|
1706
1860
|
'Export CSV',
|
|
1707
1861
|
]),
|
|
1862
|
+
// Bulk Field Update Dropdown
|
|
1863
|
+
m(BulkFieldUpdateDropdown, {
|
|
1864
|
+
modelName: modelName,
|
|
1865
|
+
selectedIds: Array.from(state.selectedRecords),
|
|
1866
|
+
onComplete: () => {
|
|
1867
|
+
state.selectedRecords = new Set();
|
|
1868
|
+
loadRecords(modelName, state.pagination.page);
|
|
1869
|
+
},
|
|
1870
|
+
}),
|
|
1708
1871
|
m('button.px-3.py-1.5.text-sm.text-gray-500.hover:text-gray-700', {
|
|
1709
1872
|
onclick: () => {
|
|
1710
1873
|
state.selectedRecords = new Set();
|
|
1874
|
+
state.bulkFieldDropdownOpen = false;
|
|
1875
|
+
state.selectedBulkField = null;
|
|
1711
1876
|
m.redraw();
|
|
1712
1877
|
},
|
|
1713
1878
|
}, 'Clear'),
|
|
@@ -151,6 +151,131 @@ function createExtensionApiHandlers(options) {
|
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Bulk update field values (for enum/boolean fields)
|
|
156
|
+
*/
|
|
157
|
+
async function bulkUpdateFieldHandler(req, res) {
|
|
158
|
+
try {
|
|
159
|
+
const { model: modelName } = req.params;
|
|
160
|
+
const { ids, field, value } = req.body;
|
|
161
|
+
|
|
162
|
+
if (!ids || !Array.isArray(ids) || ids.length === 0) {
|
|
163
|
+
return res.status(400).json({ error: 'No records selected' });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (!field) {
|
|
167
|
+
return res.status(400).json({ error: 'Field name is required' });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const { getModel } = require('../../../core/orm/model');
|
|
171
|
+
const model = db.getModel ? db.getModel(modelName) : getModel(modelName);
|
|
172
|
+
|
|
173
|
+
if (!model || !model.admin?.enabled) {
|
|
174
|
+
return res.status(404).json({ error: 'Model not found or not enabled' });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Get field metadata
|
|
178
|
+
const column = model.columns.get(field);
|
|
179
|
+
if (!column) {
|
|
180
|
+
return res.status(400).json({ error: `Field "${field}" not found in model` });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Validate field type - only allow enum and boolean
|
|
184
|
+
const columnMeta = column._meta || {};
|
|
185
|
+
const isEnum = columnMeta.enum && Array.isArray(columnMeta.enum);
|
|
186
|
+
const isBoolean = columnMeta.type === 'boolean' || column._def?.typeName === 'ZodBoolean';
|
|
187
|
+
|
|
188
|
+
if (!isEnum && !isBoolean) {
|
|
189
|
+
return res.status(400).json({ error: `Field "${field}" is not an enum or boolean type` });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Validate value for enum fields
|
|
193
|
+
if (isEnum && !columnMeta.enum.includes(value)) {
|
|
194
|
+
return res.status(400).json({ error: `Invalid value "${value}" for enum field "${field}"` });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Coerce boolean value
|
|
198
|
+
let updateValue = value;
|
|
199
|
+
if (isBoolean) {
|
|
200
|
+
updateValue = value === true || value === 'true' || value === 1 || value === '1';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Perform bulk update
|
|
204
|
+
const repo = db.getRepository(modelName);
|
|
205
|
+
let updated = 0;
|
|
206
|
+
|
|
207
|
+
for (const id of ids) {
|
|
208
|
+
try {
|
|
209
|
+
await repo.update(id, { [field]: updateValue });
|
|
210
|
+
updated++;
|
|
211
|
+
} catch (e) {
|
|
212
|
+
console.error(`Failed to update record ${id}:`, e.message);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
res.json({
|
|
217
|
+
success: true,
|
|
218
|
+
result: {
|
|
219
|
+
message: `${updated} of ${ids.length} records updated`,
|
|
220
|
+
updated,
|
|
221
|
+
field,
|
|
222
|
+
value: updateValue,
|
|
223
|
+
},
|
|
224
|
+
affected: updated,
|
|
225
|
+
});
|
|
226
|
+
} catch (error) {
|
|
227
|
+
console.error('Bulk update field error:', error);
|
|
228
|
+
res.status(500).json({ error: error.message });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Get bulk-updatable fields for a model (enum and boolean fields)
|
|
234
|
+
*/
|
|
235
|
+
function bulkFieldsHandler(req, res) {
|
|
236
|
+
try {
|
|
237
|
+
const { model: modelName } = req.params;
|
|
238
|
+
|
|
239
|
+
const { getModel } = require('../../../core/orm/model');
|
|
240
|
+
const model = db.getModel ? db.getModel(modelName) : getModel(modelName);
|
|
241
|
+
|
|
242
|
+
if (!model || !model.admin?.enabled) {
|
|
243
|
+
return res.status(404).json({ error: 'Model not found or not enabled' });
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const bulkFields = [];
|
|
247
|
+
|
|
248
|
+
for (const [fieldName, column] of model.columns.entries()) {
|
|
249
|
+
const columnMeta = column._meta || {};
|
|
250
|
+
const isEnum = columnMeta.enum && Array.isArray(columnMeta.enum);
|
|
251
|
+
const isBoolean = columnMeta.type === 'boolean' || column._def?.typeName === 'ZodBoolean';
|
|
252
|
+
|
|
253
|
+
if (isEnum) {
|
|
254
|
+
bulkFields.push({
|
|
255
|
+
name: fieldName,
|
|
256
|
+
type: 'enum',
|
|
257
|
+
label: columnMeta.label || fieldName,
|
|
258
|
+
options: columnMeta.enum.map(v => ({ value: v, label: v })),
|
|
259
|
+
});
|
|
260
|
+
} else if (isBoolean) {
|
|
261
|
+
bulkFields.push({
|
|
262
|
+
name: fieldName,
|
|
263
|
+
type: 'boolean',
|
|
264
|
+
label: columnMeta.label || fieldName,
|
|
265
|
+
options: [
|
|
266
|
+
{ value: true, label: 'True' },
|
|
267
|
+
{ value: false, label: 'False' },
|
|
268
|
+
],
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
res.json({ fields: bulkFields });
|
|
274
|
+
} catch (error) {
|
|
275
|
+
res.status(500).json({ error: error.message });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
154
279
|
/**
|
|
155
280
|
* Dashboard stats
|
|
156
281
|
*/
|
|
@@ -188,11 +313,25 @@ function createExtensionApiHandlers(options) {
|
|
|
188
313
|
|
|
189
314
|
/**
|
|
190
315
|
* Export records (CSV/JSON)
|
|
316
|
+
* Supports both GET (with ids in query) and POST (with ids in body)
|
|
191
317
|
*/
|
|
192
318
|
async function exportHandler(req, res) {
|
|
193
319
|
try {
|
|
194
|
-
|
|
195
|
-
const
|
|
320
|
+
// Support both path param and query param for model name
|
|
321
|
+
const modelName = req.params.model || req.query.model;
|
|
322
|
+
const format = req.query.format || 'json';
|
|
323
|
+
|
|
324
|
+
// Support IDs from query string (GET) or body (POST)
|
|
325
|
+
let idList = null;
|
|
326
|
+
if (req.body?.ids && Array.isArray(req.body.ids)) {
|
|
327
|
+
idList = req.body.ids;
|
|
328
|
+
} else if (req.query.ids) {
|
|
329
|
+
idList = req.query.ids.split(',');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (!modelName) {
|
|
333
|
+
return res.status(400).json({ error: 'Model name is required' });
|
|
334
|
+
}
|
|
196
335
|
|
|
197
336
|
const { getModel } = require('../../../core/orm/model');
|
|
198
337
|
const model = db.getModel ? db.getModel(modelName) : getModel(modelName);
|
|
@@ -205,8 +344,7 @@ function createExtensionApiHandlers(options) {
|
|
|
205
344
|
let records;
|
|
206
345
|
|
|
207
346
|
// If specific IDs provided, fetch those
|
|
208
|
-
if (
|
|
209
|
-
const idList = ids.split(',');
|
|
347
|
+
if (idList && idList.length > 0) {
|
|
210
348
|
records = [];
|
|
211
349
|
for (const id of idList) {
|
|
212
350
|
const record = await repo.findById(id);
|
|
@@ -224,23 +362,26 @@ function createExtensionApiHandlers(options) {
|
|
|
224
362
|
return columns.map(col => {
|
|
225
363
|
const val = record[col];
|
|
226
364
|
if (val === null || val === undefined) return '';
|
|
227
|
-
if (typeof val === 'string' && (val.includes(',') || val.includes('"'))) {
|
|
365
|
+
if (typeof val === 'string' && (val.includes(',') || val.includes('"') || val.includes('\n'))) {
|
|
228
366
|
return `"${val.replace(/"/g, '""')}"`;
|
|
229
367
|
}
|
|
368
|
+
if (typeof val === 'object') {
|
|
369
|
+
return `"${JSON.stringify(val).replace(/"/g, '""')}"`;
|
|
370
|
+
}
|
|
230
371
|
return String(val);
|
|
231
372
|
}).join(',');
|
|
232
373
|
});
|
|
233
374
|
|
|
375
|
+
const csvContent = [header, ...rows].join('\n');
|
|
234
376
|
res.setHeader('Content-Type', 'text/csv');
|
|
235
377
|
res.setHeader('Content-Disposition', `attachment; filename="${modelName}_export.csv"`);
|
|
236
|
-
res.
|
|
378
|
+
res.json({ data: csvContent, format: 'csv' });
|
|
237
379
|
} else {
|
|
238
380
|
// JSON export
|
|
239
|
-
res.setHeader('Content-Type', 'application/json');
|
|
240
|
-
res.setHeader('Content-Disposition', `attachment; filename="${modelName}_export.json"`);
|
|
241
381
|
res.json({ data: records, model: modelName, exportedAt: new Date().toISOString() });
|
|
242
382
|
}
|
|
243
383
|
} catch (error) {
|
|
384
|
+
console.error('Export error:', error);
|
|
244
385
|
res.status(500).json({ error: error.message });
|
|
245
386
|
}
|
|
246
387
|
}
|
|
@@ -284,6 +425,8 @@ function createExtensionApiHandlers(options) {
|
|
|
284
425
|
widgetDataHandler,
|
|
285
426
|
actionHandler,
|
|
286
427
|
bulkActionHandler,
|
|
428
|
+
bulkUpdateFieldHandler,
|
|
429
|
+
bulkFieldsHandler,
|
|
287
430
|
dashboardStatsHandler,
|
|
288
431
|
exportHandler,
|
|
289
432
|
activityLogHandler,
|
|
@@ -177,7 +177,12 @@ function adminPanelPlugin(options = {}) {
|
|
|
177
177
|
ctx.addRoute('get', `${adminPath}/api/extensions/dashboard/stats`, requireAuth, extensionHandlers.dashboardStatsHandler);
|
|
178
178
|
ctx.addRoute('post', `${adminPath}/api/extensions/actions/:actionId/:model/:id`, requireAuth, extensionHandlers.actionHandler);
|
|
179
179
|
ctx.addRoute('post', `${adminPath}/api/extensions/bulk-actions/:actionId/:model`, requireAuth, extensionHandlers.bulkActionHandler);
|
|
180
|
+
ctx.addRoute('get', `${adminPath}/api/extensions/bulk-fields/:model`, requireAuth, extensionHandlers.bulkFieldsHandler);
|
|
181
|
+
ctx.addRoute('post', `${adminPath}/api/extensions/bulk-update/:model`, requireAuth, extensionHandlers.bulkUpdateFieldHandler);
|
|
180
182
|
ctx.addRoute('get', `${adminPath}/api/extensions/export/:model`, requireAuth, extensionHandlers.exportHandler);
|
|
183
|
+
ctx.addRoute('get', `${adminPath}/api/extensions/export`, requireAuth, extensionHandlers.exportHandler);
|
|
184
|
+
ctx.addRoute('post', `${adminPath}/api/extensions/export/:model`, requireAuth, extensionHandlers.exportHandler);
|
|
185
|
+
ctx.addRoute('post', `${adminPath}/api/extensions/export`, requireAuth, extensionHandlers.exportHandler);
|
|
181
186
|
ctx.addRoute('get', `${adminPath}/api/extensions/activity`, requireAuth, extensionHandlers.activityLogHandler);
|
|
182
187
|
|
|
183
188
|
// Custom pages API routes
|