adminizer 4.6.0 → 4.7.0-build.221
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/Readme.md +12 -0
- package/assets/RelationDialogStack-CnHFOYyR.css +1 -0
- package/assets/RelationDialogStack-DI2bZxQX.js +20 -0
- package/assets/add-4213XScp.js +1 -0
- package/assets/add-group--F1XxfJ1.js +1 -0
- package/assets/add-group-form-DKRXC2n-.js +1 -0
- package/assets/add-user-b9HfMq7z.js +1 -0
- package/assets/add-user-form-UXAToWQd.js +1 -0
- package/assets/app-Clx8f5hd.css +1 -0
- package/assets/app-layout-C02ja4OI.js +1 -0
- package/assets/app-shell-D8wtkECO.js +1 -0
- package/assets/app-sidebar-BR5_zwxj.js +1 -0
- package/assets/app-sidebar-header-BZbIBQjq.js +1 -0
- package/assets/app.js +59 -7990
- package/assets/breadcrumbs-CODaqVkB.js +1 -0
- package/assets/catalog-6ZZjWdFs.js +6 -0
- package/assets/dashboard-CftAdKiH.js +3 -0
- package/assets/date-picker-DSOkNiNo.js +1 -0
- package/assets/del-modal-Cd4M7af8.js +1 -0
- package/assets/dynamic-controls-BsrJhPDY.js +1 -0
- package/assets/geo-json-CzR5JBXL.js +1 -0
- package/assets/handsontable-BI98OF5R.js +336 -0
- package/assets/history-DLHSU7Ak.js +1 -0
- package/assets/home-NcQ7nnU0.js +2 -0
- package/assets/icon-C2JNaLxH.js +1 -0
- package/assets/init-user-D2HSyXup.js +1 -0
- package/assets/input-error-CH2caTtO.js +1 -0
- package/assets/jsoneditor-DVNQmA6G.js +3869 -0
- package/assets/list-CFU0-TgN.js +4 -0
- package/assets/login-DFSLEO17.js +1 -0
- package/assets/manifest.json +246 -273
- package/assets/material-icon-Bx9pGQiQ.js +1 -0
- package/assets/media-manager-WcKe1EvD.js +6 -0
- package/assets/module-CT7PC6pc.js +1 -0
- package/assets/monaco-Bl4zdWCn.js +11 -0
- package/assets/nav-footer-BiSQkPt4.js +1 -0
- package/assets/nav-main-rTH0PfDw.js +1 -0
- package/assets/nav-user-Bj6cWs0a.js +1 -0
- package/assets/notification-Bi-PX87D.js +20 -0
- package/assets/pagination-render-AKJvTIRA.js +1 -0
- package/assets/register-BTP4uxsK.js +1 -0
- package/assets/ru-eW0WKkga.js +1 -0
- package/assets/theme-switcher-Dm8EOGzx.js +1 -0
- package/assets/toast-editor-DOg_gvSp.js +668 -0
- package/assets/user-filters-list-YUfR4XMI.js +1 -0
- package/assets/user-info-BgSbT02v.js +1 -0
- package/assets/user-menu-content-TQTXHIv6.js +1 -0
- package/assets/{vanilla-picker-l5rcX3cq.js → vanilla-picker-B3LgXoKV.js} +1 -7
- package/assets/welcome-vLwTu1R6.js +1 -0
- package/controllers/add.d.ts +1 -1
- package/controllers/add.js +20 -26
- package/controllers/addGroup.d.ts +1 -1
- package/controllers/addGroup.js +7 -15
- package/controllers/addUser.d.ts +1 -1
- package/controllers/addUser.js +10 -16
- package/controllers/catalog/Catalog.d.ts +1 -1
- package/controllers/catalog/Catalog.js +1 -10
- package/controllers/catalog/FrontendCatalogAdapter.d.ts +3 -3
- package/controllers/catalog/FrontendCatalogAdapter.js +0 -6
- package/controllers/catalog/FrontentCatalogAdapter.d.ts +3 -3
- package/controllers/catalog/FrontentCatalogAdapter.js +0 -4
- package/controllers/ckeditorUpload.js +2 -18
- package/controllers/dashboard.d.ts +1 -1
- package/controllers/dashboard.js +2 -5
- package/controllers/edit.js +24 -37
- package/controllers/editGroup.d.ts +1 -1
- package/controllers/editGroup.js +10 -21
- package/controllers/editUser.d.ts +1 -1
- package/controllers/editUser.js +11 -23
- package/controllers/exportData.d.ts +6 -0
- package/controllers/exportData.js +315 -0
- package/controllers/feed.d.ts +6 -0
- package/controllers/feed.js +73 -0
- package/controllers/feedback/feedbackController.d.ts +1 -1
- package/controllers/feedback/feedbackController.js +5 -7
- package/controllers/filter-fields/columns.d.ts +11 -0
- package/controllers/filter-fields/columns.js +106 -0
- package/controllers/filter-fields/filterFields.d.ts +10 -0
- package/controllers/filter-fields/filterFields.js +141 -0
- package/controllers/filter-fields/getAllUserFilters.d.ts +6 -0
- package/controllers/filter-fields/getAllUserFilters.js +115 -0
- package/controllers/filter-fields/groups.d.ts +5 -0
- package/controllers/filter-fields/groups.js +26 -0
- package/controllers/filter-fields/savedFilters.d.ts +25 -0
- package/controllers/filter-fields/savedFilters.js +317 -0
- package/controllers/getAllWidgets.d.ts +1 -1
- package/controllers/getAllWidgets.js +1 -9
- package/controllers/history-actions/HistoryController.js +6 -31
- package/controllers/initUser.js +6 -5
- package/controllers/inlineUpdate.d.ts +7 -0
- package/controllers/inlineUpdate.js +148 -0
- package/controllers/list.d.ts +1 -1
- package/controllers/list.js +383 -70
- package/controllers/listUserFilters.d.ts +5 -0
- package/controllers/listUserFilters.js +26 -0
- package/controllers/login.js +2 -2
- package/controllers/media-manager/mediaManagerApi.js +0 -9
- package/controllers/navigation/edit.js +3 -9
- package/controllers/notifications/NotificationController.d.ts +1 -1
- package/controllers/notifications/NotificationController.js +12 -36
- package/controllers/register.js +10 -9
- package/controllers/remove.js +10 -19
- package/controllers/timezones.d.ts +1 -1
- package/controllers/timezones.js +0 -6
- package/controllers/userApiKey.d.ts +7 -0
- package/controllers/userApiKey.js +36 -0
- package/controllers/view.js +14 -26
- package/controllers/welcome.d.ts +1 -1
- package/controllers/welcome.js +0 -4
- package/controllers/widgets/Action.d.ts +1 -1
- package/controllers/widgets/Action.js +1 -9
- package/controllers/widgets/Custom.d.ts +1 -1
- package/controllers/widgets/Custom.js +1 -9
- package/controllers/widgets/Info.d.ts +1 -1
- package/controllers/widgets/Info.js +1 -9
- package/controllers/widgets/filterInfo.d.ts +1 -0
- package/controllers/widgets/filterInfo.js +44 -0
- package/controllers/widgets/switch.d.ts +1 -1
- package/controllers/widgets/switch.js +1 -9
- package/controllers/widgetsDB.d.ts +1 -1
- package/controllers/widgetsDB.js +2 -8
- package/helpers/accessRightsHelper.d.ts +1 -1
- package/helpers/accessRightsHelper.js +5 -4
- package/helpers/apiKeyHelper.d.ts +5 -0
- package/helpers/apiKeyHelper.js +8 -0
- package/helpers/configHelper.js +12 -15
- package/helpers/controllerHelper.d.ts +14 -21
- package/helpers/controllerHelper.js +50 -83
- package/helpers/fieldsHelper.js +3 -3
- package/helpers/filterDatetimeHelper.d.ts +5 -0
- package/helpers/filterDatetimeHelper.js +84 -0
- package/helpers/inertiaActionsHelper.d.ts +2 -2
- package/helpers/inertiaActionsHelper.js +5 -4
- package/helpers/inertiaAddHelper.d.ts +3 -414
- package/helpers/inertiaAddHelper.js +39 -15
- package/helpers/inertiaGroupHelper.d.ts +2 -2
- package/helpers/inertiaGroupHelper.js +3 -3
- package/helpers/inertiaListHelper.d.ts +7 -3
- package/helpers/inertiaListHelper.js +26 -12
- package/helpers/inertiaMenuHelper.d.ts +1 -0
- package/helpers/inertiaMenuHelper.js +15 -3
- package/helpers/inertiaUserHelper.d.ts +3 -2
- package/helpers/inertiaUserHelper.js +7 -3
- package/helpers/menuHelper.d.ts +5 -2
- package/helpers/menuHelper.js +64 -12
- package/helpers/viteHelper.js +1 -2
- package/index.d.ts +6 -2
- package/index.js +6 -2
- package/interfaces/adminpanelConfig.d.ts +126 -39
- package/interfaces/internalModelAccess.d.ts +37 -0
- package/interfaces/internalModelAccess.js +1 -0
- package/interfaces/listQueryBuilder.d.ts +22 -0
- package/interfaces/listQueryBuilder.js +1 -0
- package/interfaces/queryCriteria.d.ts +54 -0
- package/interfaces/queryCriteria.js +1 -0
- package/interfaces/types.d.ts +1 -13
- package/lib/Adminizer.d.ts +4 -2
- package/lib/Adminizer.js +29 -10
- package/lib/DataAccessor.d.ts +8 -5
- package/lib/DataAccessor.js +89 -42
- package/lib/I18n.d.ts +5 -1
- package/lib/I18n.js +45 -19
- package/lib/MiddlewareManager.d.ts +8 -0
- package/lib/MiddlewareManager.js +53 -0
- package/lib/ai-assistant/OpenAiModelService.d.ts +3 -3
- package/lib/ai-assistant/OpenAiModelService.js +23 -24
- package/lib/catalog/Navigation.d.ts +1 -0
- package/lib/catalog/Navigation.js +12 -11
- package/lib/controls/jsoneditor/JsonEditor.d.ts +1 -2
- package/lib/controls/markdown/ToastUiEditor.d.ts +1 -2
- package/lib/feedback/FeedbackHandler.js +2 -2
- package/lib/filters/ConditionValidator.d.ts +107 -0
- package/lib/filters/ConditionValidator.js +483 -0
- package/lib/filters/CustomFilterHandler.d.ts +122 -0
- package/lib/filters/CustomFilterHandler.js +131 -0
- package/lib/filters/FilterCustomFieldHandler.d.ts +2 -0
- package/lib/filters/FilterCustomFieldHandler.js +2 -0
- package/lib/filters/FilterService.d.ts +82 -0
- package/lib/filters/FilterService.js +323 -0
- package/lib/filters/index.d.ts +6 -0
- package/lib/filters/index.js +4 -0
- package/lib/history-actions/AbstractHistoryAdapter.d.ts +5 -4
- package/lib/history-actions/AbstractHistoryAdapter.js +45 -30
- package/lib/history-actions/DefaultHistoryAdapter.d.ts +2 -0
- package/lib/history-actions/DefaultHistoryAdapter.js +12 -6
- package/lib/history-actions/HistoryHandler.js +1 -1
- package/lib/list-query-builder/ListQueryBuilder.d.ts +117 -0
- package/lib/list-query-builder/ListQueryBuilder.js +707 -0
- package/lib/media-manager/AbstractMediaManager.d.ts +9 -8
- package/lib/media-manager/DefaultMediaManager.d.ts +2 -0
- package/lib/media-manager/DefaultMediaManager.js +27 -15
- package/lib/media-manager/Items.d.ts +4 -1
- package/lib/media-manager/Items.js +62 -59
- package/lib/media-manager/MediaManagerHandler.js +1 -1
- package/lib/media-manager/helpers/MediaManagerHelper.d.ts +5 -1
- package/lib/media-manager/helpers/MediaManagerHelper.js +38 -4
- package/lib/model/AbstractModel.d.ts +33 -25
- package/lib/model/AbstractModel.js +54 -2
- package/lib/model/InternalModelAccessFactory.d.ts +18 -0
- package/lib/model/InternalModelAccessFactory.js +82 -0
- package/lib/model/ModelHandler.d.ts +6 -0
- package/lib/model/ModelHandler.js +14 -0
- package/lib/model/adapter/sequelize.d.ts +32 -10
- package/lib/model/adapter/sequelize.js +409 -62
- package/lib/model/adapter/typeorm.d.ts +62 -0
- package/lib/model/adapter/typeorm.js +777 -0
- package/lib/model/internalModelAccessToken.d.ts +1 -0
- package/lib/model/internalModelAccessToken.js +1 -0
- package/lib/model/internalModelRepository.d.ts +3 -0
- package/lib/model/internalModelRepository.js +4 -0
- package/lib/notifications/AbstractNotificationService.d.ts +3 -0
- package/lib/notifications/AbstractNotificationService.js +42 -36
- package/lib/notifications/GeneralNotificationService.js +37 -40
- package/lib/notifications/SystemNotificationService.js +33 -36
- package/lib/requestProcessor.js +0 -3
- package/lib/ui-i18n/getUiTranslations.d.ts +1 -0
- package/lib/ui-i18n/getUiTranslations.js +3 -0
- package/lib/ui-i18n/uiTranslationKeys.d.ts +6 -0
- package/lib/ui-i18n/uiTranslationKeys.js +268 -0
- package/lib/widgets/widgetHandler.d.ts +7 -2
- package/lib/widgets/widgetHandler.js +98 -18
- package/models/FilterAP.d.ts +116 -0
- package/models/FilterAP.js +68 -0
- package/models/FilterColumnAP.d.ts +32 -0
- package/models/FilterColumnAP.js +21 -0
- package/models/GroupAP.d.ts +1 -1
- package/models/HistoryActionsAP.d.ts +3 -3
- package/models/MediaManagerMetaAP.d.ts +1 -1
- package/models/NavigationAP.d.ts +1 -1
- package/models/UserAP.d.ts +6 -1
- package/models/UserAP.js +4 -0
- package/package.json +39 -32
- package/policies/authPolicies.d.ts +12 -0
- package/policies/authPolicies.js +104 -0
- package/policies/permissionResolvers.d.ts +12 -0
- package/policies/permissionResolvers.js +52 -0
- package/services/FeedService.d.ts +70 -0
- package/services/FeedService.js +334 -0
- package/system/Router.d.ts +1 -0
- package/system/Router.js +136 -59
- package/system/bindAccessRights.js +11 -30
- package/system/bindAuthorization.js +18 -18
- package/system/bindCors.js +16 -1
- package/system/bindCustomFilterHandlers.d.ts +2 -0
- package/system/bindCustomFilterHandlers.js +4 -0
- package/system/bindInertia.js +18 -61
- package/system/bindReqFunctions.js +10 -2
- package/system/bindTranslations.js +7 -3
- package/system/buildInternalModelAccess.d.ts +4 -0
- package/system/buildInternalModelAccess.js +59 -0
- package/system/defaults.js +18 -14
- package/system/routePolicies.d.ts +2 -0
- package/system/routePolicies.js +29 -0
- package/translations/en.json +151 -39
- package/translations/ru.json +211 -64
- package/ui/components/handsontable.js +13 -27
- package/ui/components/ui/dialog-stack.js +1 -3
- package/ui/components/ui/popover.js +1 -1
- package/ui/components/ui/sidebar.js +1 -1
- package/assets/RelationDialogStack-D9UVEF9s.js +0 -407
- package/assets/RelationDialogStack-DV8OITPR.css +0 -4
- package/assets/add-BlvzWQKF.js +0 -1
- package/assets/add-group-1Uoq9NWe.js +0 -1
- package/assets/add-group-form-B01gwDGu.js +0 -1
- package/assets/add-user-BQCAsaRy.js +0 -1
- package/assets/add-user-form-Cgwc9JkI.js +0 -1
- package/assets/app-DaOMy1nl.css +0 -72
- package/assets/app-layout-CAm60qoE.js +0 -1
- package/assets/app-shell-QWCtn9ye.js +0 -1
- package/assets/app-sidebar-CfaYbnSy.js +0 -1
- package/assets/app-sidebar-header-BsPFDhZ8.js +0 -1
- package/assets/breadcrumbs-CZZirDj8.js +0 -1
- package/assets/catalog-k5myk7fk.js +0 -6
- package/assets/dashboard-BkL9ybBw.js +0 -3
- package/assets/date-picker-B5AZ_7Q9.js +0 -1
- package/assets/del-modal-BoyXZloG.js +0 -1
- package/assets/dynamic-controls-k61T9W2P.js +0 -1
- package/assets/form-1WGY0YgG.js +0 -1
- package/assets/form-DJapG3qF.js +0 -1
- package/assets/geo-json-DsMUHo0l.js +0 -4
- package/assets/handsontable-PxYdjN8c.js +0 -298
- package/assets/history-CluyR-yJ.js +0 -1
- package/assets/home-Dug9zBzM.js +0 -2
- package/assets/icon-DfDLMG5f.js +0 -1
- package/assets/init-user-CF80MEnT.js +0 -1
- package/assets/input-error-DuuqFgZa.js +0 -1
- package/assets/jsoneditor-De1FvcYu.js +0 -3882
- package/assets/list-CuexshNt.js +0 -1
- package/assets/list-table-BsEWjTKY.js +0 -22
- package/assets/login-eDemYzN-.js +0 -1
- package/assets/material-icon-DMT8KdmE.js +0 -1
- package/assets/media-manager-DfYa_mT_.js +0 -14
- package/assets/module-Cd4Tm6uq.js +0 -1
- package/assets/monaco-BhBYtg3d.js +0 -11
- package/assets/nav-footer-D3olYxtz.js +0 -1
- package/assets/nav-main-BctqyzeV.js +0 -1
- package/assets/nav-user-Bn1Lt-BZ.js +0 -1
- package/assets/notification-D64qvni-.js +0 -27
- package/assets/pagination-render-DxD78ahJ.js +0 -1
- package/assets/register-349PwYbe.js +0 -1
- package/assets/theme-switcher-DfExsHW2.js +0 -1
- package/assets/toast-editor-ESK5iWPx.js +0 -845
- package/assets/user-info-DtBK5Kh8.js +0 -1
- package/assets/user-menu-content-0XYOCMaA.js +0 -1
- package/assets/welcome-PDp-gUei.js +0 -1
- package/controllers/form.d.ts +0 -1
- package/controllers/form.js +0 -86
- package/helpers/formHelper.d.ts +0 -6
- package/helpers/formHelper.js +0 -38
- package/helpers/inertiaFromHelper.d.ts +0 -11
- package/helpers/inertiaFromHelper.js +0 -98
- package/helpers/runtimeMode.d.ts +0 -1
- package/helpers/runtimeMode.js +0 -3
- package/lib/PolicyManager.d.ts +0 -8
- package/lib/PolicyManager.js +0 -53
- package/lib/datatable/NodeTable.d.ts +0 -49
- package/lib/datatable/NodeTable.js +0 -207
- package/lib/model/adapter/waterline.d.ts +0 -32
- package/lib/model/adapter/waterline.js +0 -180
- package/migrations/index.d.ts +0 -3
- package/migrations/index.js +0 -4
- package/migrations/knex/20250106000001_adminizer_init.d.ts +0 -8
- package/migrations/knex/20250106000001_adminizer_init.js +0 -164
- package/migrations/umzug/0001.d.ts +0 -7
- package/migrations/umzug/0001.js +0 -121
- package/migrations/umzugExports.d.ts +0 -2
- package/migrations/umzugExports.js +0 -9
- package/system/bindForms.d.ts +0 -2
- package/system/bindForms.js +0 -20
- /package/{policies → middlewares}/addMessages.d.ts +0 -0
- /package/{policies → middlewares}/addMessages.js +0 -0
- /package/{policies → middlewares}/checkAuth.d.ts +0 -0
- /package/{policies → middlewares}/checkAuth.js +0 -0
- /package/{policies → middlewares}/setLocale.d.ts +0 -0
- /package/{policies → middlewares}/setLocale.js +0 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { Adminizer } from "../lib/Adminizer.js";
|
|
2
|
+
import { DataAccessor } from "../lib/DataAccessor.js";
|
|
3
|
+
import { ListQueryBuilder } from "../lib/list-query-builder/ListQueryBuilder.js";
|
|
4
|
+
import { ControllerHelper } from "../helpers/controllerHelper.js";
|
|
5
|
+
import { convertDatetimeConditions } from "../helpers/filterDatetimeHelper.js";
|
|
6
|
+
/**
|
|
7
|
+
* FeedService — generates public feed exports (Atom XML, JSON) by apiKey
|
|
8
|
+
* No authentication required — access is granted via apiKey alone
|
|
9
|
+
*/
|
|
10
|
+
export class FeedService {
|
|
11
|
+
adminizer;
|
|
12
|
+
constructor(adminizer) {
|
|
13
|
+
this.adminizer = adminizer;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Find filter by apiKey and verify it's enabled
|
|
17
|
+
*/
|
|
18
|
+
async findFilterByApiKey(apiKey) {
|
|
19
|
+
if (!apiKey) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return await this.adminizer.modelHandler
|
|
23
|
+
.internal("feed")
|
|
24
|
+
.get("FilterAP")
|
|
25
|
+
.findOne({ where: { apiKey, apiEnabled: true, visibility: 'private' } });
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Fetch data for a filter (no auth required — public API)
|
|
29
|
+
* Bypasses permission checks by building fields directly from model config
|
|
30
|
+
* Also bypasses DataAccessor to avoid user-based filtering
|
|
31
|
+
*/
|
|
32
|
+
async fetchFilterData(filter, adminizer, user) {
|
|
33
|
+
const modelName = filter.modelName;
|
|
34
|
+
const adminizerInstance = adminizer || this.adminizer;
|
|
35
|
+
if (!user)
|
|
36
|
+
throw 'User is not defined';
|
|
37
|
+
Adminizer.log.info(`FeedService: === START fetchFilterData === filter=${filter.id}, model=${modelName}`);
|
|
38
|
+
// Create a minimal request-like object to use ControllerHelper
|
|
39
|
+
const mockReq = {
|
|
40
|
+
adminizer: adminizerInstance,
|
|
41
|
+
params: { modelResourceName: modelName },
|
|
42
|
+
query: {},
|
|
43
|
+
originalUrl: `/adminizer/model/${modelName}/list`,
|
|
44
|
+
url: `/adminizer/model/${modelName}/list`
|
|
45
|
+
};
|
|
46
|
+
// Get ModelResource for model
|
|
47
|
+
const modelResource = ControllerHelper.findModelResource(mockReq);
|
|
48
|
+
if (!modelResource || !modelResource.model || !modelResource.model.attributes) {
|
|
49
|
+
throw new Error(`Model '${modelName}' not found`);
|
|
50
|
+
}
|
|
51
|
+
// Build fields directly from model attributes (bypass permission checks for public API)
|
|
52
|
+
const fields = this.buildFieldsFromAttributes(modelResource.model.attributes, modelResource.config?.fields || {}, adminizerInstance);
|
|
53
|
+
// Use saved columns if available
|
|
54
|
+
let displayFields = fields;
|
|
55
|
+
if (filter.columns && filter.columns.length > 0) {
|
|
56
|
+
displayFields = this.applyCustomColumns(fields, filter.columns);
|
|
57
|
+
}
|
|
58
|
+
// Convert datetime conditions (same as exportData.ts)
|
|
59
|
+
const rawConditions = filter.conditions || [];
|
|
60
|
+
const convertedConditions = convertDatetimeConditions(rawConditions, { dropEmptyValues: true });
|
|
61
|
+
const createdAtSortField = this.resolveCreatedAtSortField(modelResource.model.attributes, displayFields);
|
|
62
|
+
// Build query params for where clause construction
|
|
63
|
+
const queryParams = {
|
|
64
|
+
page: 1,
|
|
65
|
+
limit: 20,
|
|
66
|
+
filters: convertedConditions.length > 0 ? convertedConditions : undefined,
|
|
67
|
+
sort: createdAtSortField,
|
|
68
|
+
sortDirection: 'DESC',
|
|
69
|
+
fields: Object.keys(displayFields)
|
|
70
|
+
};
|
|
71
|
+
// Build where clause using ListQueryBuilder
|
|
72
|
+
// Create an administrator DataAccessor to avoid user-based filtering
|
|
73
|
+
const dataAccessor = new DataAccessor(adminizerInstance, user, modelResource, "list");
|
|
74
|
+
const listQueryBuilder = new ListQueryBuilder(modelResource.model, fields, dataAccessor, adminizerInstance.customFilterHandler);
|
|
75
|
+
const result = await listQueryBuilder.execute(queryParams);
|
|
76
|
+
Adminizer.log.info(`FeedService: === END fetchFilterData === records=${result.data?.length ?? 0}`);
|
|
77
|
+
return {
|
|
78
|
+
records: result.data,
|
|
79
|
+
fields: displayFields,
|
|
80
|
+
modelName,
|
|
81
|
+
modelResource
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Build fields object directly from model attributes (bypasses permission checks)
|
|
86
|
+
*/
|
|
87
|
+
buildFieldsFromAttributes(attributes, configFields = {}, adminizerInstance) {
|
|
88
|
+
const admin = adminizerInstance || this.adminizer;
|
|
89
|
+
const result = {};
|
|
90
|
+
for (const [key, modelField] of Object.entries(attributes)) {
|
|
91
|
+
// Skip primary keys for associations
|
|
92
|
+
if (modelField.primaryKeyForAssociation === true) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
// Handle short type notation: fieldName: 'string'
|
|
96
|
+
let normalizedField = typeof modelField === 'string' ? { type: modelField } : modelField;
|
|
97
|
+
// Get field config from model config
|
|
98
|
+
const fieldConfig = configFields[key] || {};
|
|
99
|
+
result[key] = {
|
|
100
|
+
config: fieldConfig,
|
|
101
|
+
populated: undefined,
|
|
102
|
+
model: normalizedField,
|
|
103
|
+
modelConfig: admin.config.models[Object.keys(admin.config.models).find(k => k.toLowerCase() === Object.keys(attributes)[0]?.split('.')[0])?.toLowerCase() || ''] || {}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Generate JSON feed
|
|
110
|
+
*/
|
|
111
|
+
async generateJsonFeed(filter, adminizer, user) {
|
|
112
|
+
const { records, fields, modelName } = await this.fetchFilterData(filter, adminizer, user);
|
|
113
|
+
const exportData = this.prepareExportData(records, fields);
|
|
114
|
+
return {
|
|
115
|
+
feed: {
|
|
116
|
+
title: filter.name,
|
|
117
|
+
description: filter.description || '',
|
|
118
|
+
modelName,
|
|
119
|
+
generatedAt: new Date().toISOString(),
|
|
120
|
+
totalItems: records.length,
|
|
121
|
+
apiKey: filter.apiKey,
|
|
122
|
+
items: exportData
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Generate Atom XML feed
|
|
128
|
+
*/
|
|
129
|
+
async generateAtomFeed(filter, adminizer, user) {
|
|
130
|
+
const { records, fields, modelName } = await this.fetchFilterData(filter, adminizer, user);
|
|
131
|
+
const exportData = this.prepareExportData(records, fields);
|
|
132
|
+
const entries = exportData.map((item, index) => {
|
|
133
|
+
const entryId = item.id || item.ID || index;
|
|
134
|
+
const titleField = this.findTitleField(fields);
|
|
135
|
+
const title = titleField ? String(item[titleField] || `Item ${entryId}`) : `Item ${entryId}`;
|
|
136
|
+
const updated = this.findDateField(fields, item) || new Date();
|
|
137
|
+
return {
|
|
138
|
+
id: String(entryId),
|
|
139
|
+
title,
|
|
140
|
+
updated: new Date(updated),
|
|
141
|
+
summary: title,
|
|
142
|
+
content: item,
|
|
143
|
+
link: `/adminizer/model/${modelName}/view/${entryId}`
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
return this.buildAtomXml(filter, modelName, entries, adminizer);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Build Atom XML string
|
|
150
|
+
*/
|
|
151
|
+
buildAtomXml(filter, modelName, entries, adminizerInstance) {
|
|
152
|
+
const admin = adminizerInstance || this.adminizer;
|
|
153
|
+
const now = new Date().toISOString();
|
|
154
|
+
const routePrefix = admin.config.routePrefix || '/adminizer';
|
|
155
|
+
const baseUrl = `/${routePrefix.replace(/^\//, '')}`;
|
|
156
|
+
let xml = `<?xml version="1.0" encoding="UTF-8"?>\n`;
|
|
157
|
+
xml += `<feed xmlns="http://www.w3.org/2005/Atom">\n`;
|
|
158
|
+
xml += ` <id>${baseUrl}/api/feed/${filter.apiKey}</id>\n`;
|
|
159
|
+
xml += ` <title>${this.escapeXml(filter.name)}</title>\n`;
|
|
160
|
+
xml += ` <subtitle>${this.escapeXml(filter.description || `Feed for ${modelName}`)}</subtitle>\n`;
|
|
161
|
+
xml += ` <updated>${now}</updated>\n`;
|
|
162
|
+
xml += ` <link href="${baseUrl}/api/feed/${filter.apiKey}.xml" rel="self" />\n`;
|
|
163
|
+
xml += ` <generator>Adminizer</generator>\n`;
|
|
164
|
+
for (const entry of entries) {
|
|
165
|
+
xml += ` <entry>\n`;
|
|
166
|
+
xml += ` <id>${this.escapeXml(String(entry.id))}</id>\n`;
|
|
167
|
+
xml += ` <title>${this.escapeXml(entry.title)}</title>\n`;
|
|
168
|
+
xml += ` <updated>${entry.updated.toISOString()}</updated>\n`;
|
|
169
|
+
xml += ` <summary>${this.escapeXml(entry.summary || '')}</summary>\n`;
|
|
170
|
+
if (entry.link) {
|
|
171
|
+
xml += ` <link href="${this.escapeXml(entry.link)}" rel="alternate" />\n`;
|
|
172
|
+
}
|
|
173
|
+
// Add content as structured XML
|
|
174
|
+
xml += ` <content type="html">\n`;
|
|
175
|
+
xml += ` <![CDATA[\n`;
|
|
176
|
+
xml += ` <table>\n`;
|
|
177
|
+
for (const [key, value] of Object.entries(entry.content)) {
|
|
178
|
+
const displayValue = value === null || value === undefined ? '' : String(value);
|
|
179
|
+
xml += ` <tr><th>${this.escapeXml(key)}</th><td>${this.escapeXml(displayValue)}</td></tr>\n`;
|
|
180
|
+
}
|
|
181
|
+
xml += ` </table>\n`;
|
|
182
|
+
xml += ` ]]>\n`;
|
|
183
|
+
xml += ` </content>\n`;
|
|
184
|
+
// Also add raw data as JSON in a custom element
|
|
185
|
+
xml += ` <data>${this.escapeXml(JSON.stringify(entry.content))}</data>\n`;
|
|
186
|
+
xml += ` </entry>\n`;
|
|
187
|
+
}
|
|
188
|
+
xml += `</feed>`;
|
|
189
|
+
return xml;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Escape XML special characters
|
|
193
|
+
*/
|
|
194
|
+
escapeXml(str) {
|
|
195
|
+
if (str === null || str === undefined)
|
|
196
|
+
return '';
|
|
197
|
+
const s = String(str);
|
|
198
|
+
return s
|
|
199
|
+
.replace(/&/g, '&')
|
|
200
|
+
.replace(/</g, '<')
|
|
201
|
+
.replace(/>/g, '>')
|
|
202
|
+
.replace(/"/g, '"')
|
|
203
|
+
.replace(/'/g, ''');
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Prepare data for export — convert values to human-readable format
|
|
207
|
+
*/
|
|
208
|
+
prepareExportData(records, fields) {
|
|
209
|
+
return records.map(record => {
|
|
210
|
+
const row = {};
|
|
211
|
+
for (const [key, field] of Object.entries(fields)) {
|
|
212
|
+
const fieldConfig = field;
|
|
213
|
+
row[key] = this.formatExportValue(fieldConfig, record[key]);
|
|
214
|
+
}
|
|
215
|
+
return row;
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Format value for export based on type
|
|
220
|
+
*/
|
|
221
|
+
formatExportValue(field, value) {
|
|
222
|
+
if (value === null || value === undefined) {
|
|
223
|
+
return '';
|
|
224
|
+
}
|
|
225
|
+
const fieldType = field.model?.type;
|
|
226
|
+
// Handle relationships
|
|
227
|
+
if (field.model?.model) {
|
|
228
|
+
const displayField = field.modelConfig?.titleField ??
|
|
229
|
+
(typeof value === 'object' && value ? (value.title || value.name || value.id) : value);
|
|
230
|
+
return typeof displayField === 'object' ? JSON.stringify(displayField) : String(displayField);
|
|
231
|
+
}
|
|
232
|
+
// Handle associations (hasMany)
|
|
233
|
+
if (fieldType === 'association-many' || fieldType === 'association') {
|
|
234
|
+
if (Array.isArray(value)) {
|
|
235
|
+
return value.map((item) => {
|
|
236
|
+
if (typeof item === 'object' && item) {
|
|
237
|
+
return item.title || item.name || item.id || JSON.stringify(item);
|
|
238
|
+
}
|
|
239
|
+
return String(item);
|
|
240
|
+
}).join(', ');
|
|
241
|
+
}
|
|
242
|
+
return String(value);
|
|
243
|
+
}
|
|
244
|
+
// Handle JSON fields
|
|
245
|
+
if (fieldType === 'json') {
|
|
246
|
+
if (typeof value === 'object') {
|
|
247
|
+
return JSON.stringify(value, null, 2);
|
|
248
|
+
}
|
|
249
|
+
return String(value);
|
|
250
|
+
}
|
|
251
|
+
// Handle boolean
|
|
252
|
+
if (fieldType === 'boolean') {
|
|
253
|
+
return value ? 'Yes' : 'No';
|
|
254
|
+
}
|
|
255
|
+
// Handle dates
|
|
256
|
+
if (fieldType === 'date' || fieldType === 'datetime') {
|
|
257
|
+
if (value instanceof Date) {
|
|
258
|
+
return value.toISOString();
|
|
259
|
+
}
|
|
260
|
+
return String(value);
|
|
261
|
+
}
|
|
262
|
+
// Handle arrays
|
|
263
|
+
if (Array.isArray(value)) {
|
|
264
|
+
return value.join(', ');
|
|
265
|
+
}
|
|
266
|
+
// Default: convert to string
|
|
267
|
+
return String(value);
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Apply custom column configuration
|
|
271
|
+
*/
|
|
272
|
+
applyCustomColumns(fields, columns) {
|
|
273
|
+
const sortedColumns = [...columns].sort((a, b) => (a.order || 0) - (b.order || 0));
|
|
274
|
+
const visibleColumns = sortedColumns.filter(col => fields[col.fieldName]);
|
|
275
|
+
if (visibleColumns.length === 0) {
|
|
276
|
+
return fields;
|
|
277
|
+
}
|
|
278
|
+
const result = {};
|
|
279
|
+
for (const col of visibleColumns) {
|
|
280
|
+
if (fields[col.fieldName]) {
|
|
281
|
+
result[col.fieldName] = fields[col.fieldName];
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return result;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Resolve the best field for "newest first" sorting.
|
|
288
|
+
* Priority: createdAt -> created_at -> id
|
|
289
|
+
*/
|
|
290
|
+
resolveCreatedAtSortField(attributes, fields) {
|
|
291
|
+
const candidates = ['createdAt', 'created_at'];
|
|
292
|
+
for (const candidate of candidates) {
|
|
293
|
+
if (attributes?.[candidate] || fields?.[candidate]) {
|
|
294
|
+
return candidate;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return 'id';
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Find title field in fields config
|
|
301
|
+
*/
|
|
302
|
+
findTitleField(fields) {
|
|
303
|
+
// First look for configured title field
|
|
304
|
+
for (const [key, field] of Object.entries(fields)) {
|
|
305
|
+
const f = field;
|
|
306
|
+
if (f.modelConfig?.titleField) {
|
|
307
|
+
return key;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// Fallback to common title field names
|
|
311
|
+
const candidates = ['title', 'name', 'label', 'caption'];
|
|
312
|
+
for (const name of candidates) {
|
|
313
|
+
if (fields[name]) {
|
|
314
|
+
return name;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Find date field in fields config
|
|
321
|
+
*/
|
|
322
|
+
findDateField(fields, record) {
|
|
323
|
+
const candidates = ['updatedAt', 'updated_at', 'modifiedAt', 'createdAt', 'created_at'];
|
|
324
|
+
for (const name of candidates) {
|
|
325
|
+
if (record[name]) {
|
|
326
|
+
const date = new Date(record[name]);
|
|
327
|
+
if (!isNaN(date.getTime())) {
|
|
328
|
+
return date;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return new Date();
|
|
333
|
+
}
|
|
334
|
+
}
|