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,483 @@
|
|
|
1
|
+
import { FILTER_SECURITY_LIMITS } from '../list-query-builder/ListQueryBuilder.js';
|
|
2
|
+
/**
|
|
3
|
+
* ConditionValidator - validates filter conditions
|
|
4
|
+
*
|
|
5
|
+
* Validates:
|
|
6
|
+
* - Field existence
|
|
7
|
+
* - Operator validity for field type
|
|
8
|
+
* - Value type and format
|
|
9
|
+
* - Security limits (depth, array size, etc.)
|
|
10
|
+
*
|
|
11
|
+
* Security logging:
|
|
12
|
+
* Set ConditionValidator.logger to enable logging of suspicious attempts
|
|
13
|
+
*/
|
|
14
|
+
export class ConditionValidator {
|
|
15
|
+
fieldsConfig;
|
|
16
|
+
allowedFields;
|
|
17
|
+
/**
|
|
18
|
+
* Static logger for security events
|
|
19
|
+
* Set this to enable logging of suspicious filter attempts
|
|
20
|
+
*/
|
|
21
|
+
static logger = null;
|
|
22
|
+
/**
|
|
23
|
+
* Collected security events during validation
|
|
24
|
+
*/
|
|
25
|
+
securityEvents = [];
|
|
26
|
+
constructor(fieldsConfig, allowedFields) {
|
|
27
|
+
this.fieldsConfig = fieldsConfig;
|
|
28
|
+
this.allowedFields = allowedFields;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Validate array of conditions
|
|
32
|
+
*/
|
|
33
|
+
validate(conditions) {
|
|
34
|
+
const errors = [];
|
|
35
|
+
const context = {
|
|
36
|
+
totalConditions: 0,
|
|
37
|
+
totalConditionsExceeded: false
|
|
38
|
+
};
|
|
39
|
+
this.securityEvents = []; // Reset for new validation
|
|
40
|
+
for (const condition of conditions) {
|
|
41
|
+
this.validateCondition(condition, errors, 0, context);
|
|
42
|
+
if (context.totalConditionsExceeded) {
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
valid: errors.length === 0,
|
|
48
|
+
errors,
|
|
49
|
+
securityEvents: this.securityEvents.length > 0 ? this.securityEvents : undefined
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Log a security event (suspicious filter attempt)
|
|
54
|
+
*/
|
|
55
|
+
logSecurityEvent(type, conditionId, details, value) {
|
|
56
|
+
const event = {
|
|
57
|
+
type,
|
|
58
|
+
conditionId,
|
|
59
|
+
details,
|
|
60
|
+
timestamp: new Date(),
|
|
61
|
+
value
|
|
62
|
+
};
|
|
63
|
+
this.securityEvents.push(event);
|
|
64
|
+
// Log to external logger if configured
|
|
65
|
+
if (ConditionValidator.logger) {
|
|
66
|
+
ConditionValidator.logger.warn(`[SECURITY] Filter validation: ${type} - ${details}` +
|
|
67
|
+
(value !== undefined ? ` (value: ${JSON.stringify(value).substring(0, 100)})` : ''));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Validate single condition recursively
|
|
72
|
+
*/
|
|
73
|
+
validateCondition(condition, errors, depth, context) {
|
|
74
|
+
if (context.totalConditionsExceeded) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
context.totalConditions += 1;
|
|
78
|
+
if (context.totalConditions > FILTER_SECURITY_LIMITS.MAX_TOTAL_CONDITIONS) {
|
|
79
|
+
context.totalConditionsExceeded = true;
|
|
80
|
+
this.logSecurityEvent('TOO_MANY_TOTAL_CONDITIONS', condition.id, `Attempted ${context.totalConditions} total conditions, max ${FILTER_SECURITY_LIMITS.MAX_TOTAL_CONDITIONS}`, context.totalConditions);
|
|
81
|
+
errors.push({
|
|
82
|
+
conditionId: condition.id,
|
|
83
|
+
message: `Too many total conditions (max ${FILTER_SECURITY_LIMITS.MAX_TOTAL_CONDITIONS})`,
|
|
84
|
+
code: 'TOO_MANY_TOTAL_CONDITIONS'
|
|
85
|
+
});
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
// Check depth limit
|
|
89
|
+
if (depth > FILTER_SECURITY_LIMITS.MAX_DEPTH) {
|
|
90
|
+
this.logSecurityEvent('MAX_DEPTH_EXCEEDED', condition.id, `Attempted nesting depth ${depth} exceeds max ${FILTER_SECURITY_LIMITS.MAX_DEPTH}`, depth);
|
|
91
|
+
errors.push({
|
|
92
|
+
conditionId: condition.id,
|
|
93
|
+
message: `Maximum nesting depth exceeded (max ${FILTER_SECURITY_LIMITS.MAX_DEPTH})`,
|
|
94
|
+
code: 'MAX_DEPTH_EXCEEDED'
|
|
95
|
+
});
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
// Group with children
|
|
99
|
+
if (condition.children && condition.children.length > 0) {
|
|
100
|
+
// Check conditions count
|
|
101
|
+
if (condition.children.length > FILTER_SECURITY_LIMITS.MAX_CONDITIONS_PER_GROUP) {
|
|
102
|
+
this.logSecurityEvent('TOO_MANY_CONDITIONS', condition.id, `Attempted ${condition.children.length} conditions in group, max ${FILTER_SECURITY_LIMITS.MAX_CONDITIONS_PER_GROUP}`, condition.children.length);
|
|
103
|
+
errors.push({
|
|
104
|
+
conditionId: condition.id,
|
|
105
|
+
message: `Too many conditions in group (max ${FILTER_SECURITY_LIMITS.MAX_CONDITIONS_PER_GROUP})`,
|
|
106
|
+
code: 'TOO_MANY_CONDITIONS'
|
|
107
|
+
});
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// Validate logic operator
|
|
111
|
+
if (condition.logic && !['AND', 'OR', 'NOT'].includes(condition.logic)) {
|
|
112
|
+
errors.push({
|
|
113
|
+
conditionId: condition.id,
|
|
114
|
+
message: `Invalid logic operator: ${condition.logic}`,
|
|
115
|
+
code: 'INVALID_LOGIC'
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
// NOT requires exactly one child
|
|
119
|
+
if (condition.logic === 'NOT' && condition.children.length !== 1) {
|
|
120
|
+
errors.push({
|
|
121
|
+
conditionId: condition.id,
|
|
122
|
+
message: 'NOT operator requires exactly one condition',
|
|
123
|
+
code: 'NOT_REQUIRES_ONE'
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
// Validate children
|
|
127
|
+
for (const child of condition.children) {
|
|
128
|
+
this.validateCondition(child, errors, depth + 1, context);
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// Custom handler condition support
|
|
133
|
+
if (condition.customHandler) {
|
|
134
|
+
if (!condition.operator) {
|
|
135
|
+
errors.push({
|
|
136
|
+
conditionId: condition.id,
|
|
137
|
+
message: 'Operator is required',
|
|
138
|
+
code: 'OPERATOR_REQUIRED'
|
|
139
|
+
});
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (condition.operator !== 'custom') {
|
|
143
|
+
errors.push({
|
|
144
|
+
conditionId: condition.id,
|
|
145
|
+
message: `Operator '${condition.operator}' is not allowed for custom handler`,
|
|
146
|
+
code: 'INVALID_OPERATOR'
|
|
147
|
+
});
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const customValue = condition.value;
|
|
151
|
+
const isEmptyCustomObject = typeof customValue === 'object'
|
|
152
|
+
&& customValue !== null
|
|
153
|
+
&& !Array.isArray(customValue)
|
|
154
|
+
&& Object.values(customValue).every((v) => v === undefined
|
|
155
|
+
|| v === null
|
|
156
|
+
|| (typeof v === 'string' && v.trim() === ''));
|
|
157
|
+
if (customValue === undefined
|
|
158
|
+
|| customValue === null
|
|
159
|
+
|| (typeof customValue === 'string' && customValue.trim() === '')
|
|
160
|
+
|| isEmptyCustomObject) {
|
|
161
|
+
errors.push({
|
|
162
|
+
conditionId: condition.id,
|
|
163
|
+
message: 'Value is required for custom filter',
|
|
164
|
+
code: 'INVALID_VALUE'
|
|
165
|
+
});
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
// Skip standard validation for raw SQL (has dedicated validation path)
|
|
171
|
+
if (condition.rawSQL) {
|
|
172
|
+
this.validateRawSQL(condition, errors);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
// Relation condition support
|
|
176
|
+
if (condition.relation || condition.relationField) {
|
|
177
|
+
if (!condition.relation || !condition.relationField) {
|
|
178
|
+
errors.push({
|
|
179
|
+
conditionId: condition.id,
|
|
180
|
+
message: 'Both relation and relationField are required',
|
|
181
|
+
code: 'RELATION_FIELDS_REQUIRED'
|
|
182
|
+
});
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (!condition.operator) {
|
|
186
|
+
errors.push({
|
|
187
|
+
conditionId: condition.id,
|
|
188
|
+
message: 'Operator is required',
|
|
189
|
+
code: 'OPERATOR_REQUIRED'
|
|
190
|
+
});
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (!['eq', 'neq'].includes(condition.operator)) {
|
|
194
|
+
errors.push({
|
|
195
|
+
conditionId: condition.id,
|
|
196
|
+
message: `Operator '${condition.operator}' is not allowed for relation conditions`,
|
|
197
|
+
code: 'INVALID_OPERATOR'
|
|
198
|
+
});
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const relationValueError = this.validateValue(condition.value, condition.operator, { type: 'string' });
|
|
202
|
+
if (relationValueError) {
|
|
203
|
+
errors.push({
|
|
204
|
+
conditionId: condition.id,
|
|
205
|
+
message: relationValueError,
|
|
206
|
+
code: 'INVALID_VALUE'
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
// Check field presence
|
|
212
|
+
if (!condition.field) {
|
|
213
|
+
errors.push({
|
|
214
|
+
conditionId: condition.id,
|
|
215
|
+
message: 'Field is required',
|
|
216
|
+
code: 'FIELD_REQUIRED'
|
|
217
|
+
});
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
// Check field is allowed
|
|
221
|
+
if (this.allowedFields && !this.allowedFields.includes(condition.field)) {
|
|
222
|
+
errors.push({
|
|
223
|
+
conditionId: condition.id,
|
|
224
|
+
field: condition.field,
|
|
225
|
+
message: `Field '${condition.field}' is not allowed for filtering`,
|
|
226
|
+
code: 'FIELD_NOT_ALLOWED'
|
|
227
|
+
});
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
// Check field exists in config
|
|
231
|
+
const fieldConfig = this.fieldsConfig[condition.field];
|
|
232
|
+
if (!fieldConfig) {
|
|
233
|
+
errors.push({
|
|
234
|
+
conditionId: condition.id,
|
|
235
|
+
field: condition.field,
|
|
236
|
+
message: `Unknown field: ${condition.field}`,
|
|
237
|
+
code: 'UNKNOWN_FIELD'
|
|
238
|
+
});
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
// Check operator presence
|
|
242
|
+
if (!condition.operator) {
|
|
243
|
+
errors.push({
|
|
244
|
+
conditionId: condition.id,
|
|
245
|
+
field: condition.field,
|
|
246
|
+
message: 'Operator is required',
|
|
247
|
+
code: 'OPERATOR_REQUIRED'
|
|
248
|
+
});
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
// Check operator validity for field type
|
|
252
|
+
if (!this.isOperatorValidForType(condition.operator, fieldConfig.type)) {
|
|
253
|
+
errors.push({
|
|
254
|
+
conditionId: condition.id,
|
|
255
|
+
field: condition.field,
|
|
256
|
+
message: `Operator '${condition.operator}' is not valid for field type '${fieldConfig.type}'`,
|
|
257
|
+
code: 'INVALID_OPERATOR'
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
// Check value
|
|
261
|
+
const valueError = this.validateValue(condition.value, condition.operator, fieldConfig);
|
|
262
|
+
if (valueError) {
|
|
263
|
+
// Log security events for specific value errors
|
|
264
|
+
if (valueError.includes('Too many values')) {
|
|
265
|
+
this.logSecurityEvent('TOO_MANY_IN_VALUES', condition.id, `IN operator with ${Array.isArray(condition.value) ? condition.value.length : 0} values exceeds limit`, Array.isArray(condition.value) ? condition.value.length : 0);
|
|
266
|
+
}
|
|
267
|
+
else if (valueError.includes('String too long')) {
|
|
268
|
+
this.logSecurityEvent('STRING_TOO_LONG', condition.id, `String value length ${typeof condition.value === 'string' ? condition.value.length : 0} exceeds limit`, typeof condition.value === 'string' ? condition.value.length : 0);
|
|
269
|
+
}
|
|
270
|
+
errors.push({
|
|
271
|
+
conditionId: condition.id,
|
|
272
|
+
field: condition.field,
|
|
273
|
+
message: valueError,
|
|
274
|
+
code: 'INVALID_VALUE'
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Check if operator is valid for field type
|
|
280
|
+
*/
|
|
281
|
+
isOperatorValidForType(operator, fieldType) {
|
|
282
|
+
const operatorsByType = {
|
|
283
|
+
string: ['eq', 'neq', 'like', 'ilike', 'startsWith', 'endsWith', 'in', 'notIn', 'isNull', 'isNotNull', 'regex'],
|
|
284
|
+
text: ['eq', 'neq', 'like', 'ilike', 'startsWith', 'endsWith', 'isNull', 'isNotNull'],
|
|
285
|
+
number: ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'between', 'in', 'notIn', 'isNull', 'isNotNull'],
|
|
286
|
+
integer: ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'between', 'in', 'notIn', 'isNull', 'isNotNull'],
|
|
287
|
+
float: ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'between', 'in', 'notIn', 'isNull', 'isNotNull'],
|
|
288
|
+
boolean: ['eq', 'neq', 'isNull', 'isNotNull'],
|
|
289
|
+
date: ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'between', 'today', 'month', 'year', 'monthBetween', 'yearBetween', 'isNull', 'isNotNull'],
|
|
290
|
+
datetime: ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'between', 'today', 'month', 'year', 'monthBetween', 'yearBetween', 'isNull', 'isNotNull'],
|
|
291
|
+
select: ['eq', 'neq', 'in', 'notIn', 'isNull', 'isNotNull'],
|
|
292
|
+
json: ['isNull', 'isNotNull', 'custom'],
|
|
293
|
+
association: ['eq', 'neq', 'in', 'notIn', 'isNull', 'isNotNull'],
|
|
294
|
+
'association-many': ['in', 'notIn', 'isNull', 'isNotNull']
|
|
295
|
+
};
|
|
296
|
+
const allowed = operatorsByType[fieldType] || operatorsByType.string;
|
|
297
|
+
return allowed.includes(operator);
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Validate value for operator and field type
|
|
301
|
+
*/
|
|
302
|
+
validateValue(value, operator, fieldConfig) {
|
|
303
|
+
// Operators without value
|
|
304
|
+
if (operator === 'isNull' || operator === 'isNotNull' || operator === 'today') {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
// Check value presence
|
|
308
|
+
if (value === undefined || value === null || value === '') {
|
|
309
|
+
return 'Value is required';
|
|
310
|
+
}
|
|
311
|
+
// between requires array of 2 elements
|
|
312
|
+
if (operator === 'between') {
|
|
313
|
+
if (!Array.isArray(value)) {
|
|
314
|
+
return 'BETWEEN requires array value';
|
|
315
|
+
}
|
|
316
|
+
if (value.length !== 2) {
|
|
317
|
+
return 'BETWEEN requires array of 2 values';
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (operator === 'month') {
|
|
321
|
+
if (typeof value !== 'string' || !/^\d{4}-\d{2}$/.test(value)) {
|
|
322
|
+
return 'MONTH requires YYYY-MM value';
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (operator === 'year') {
|
|
326
|
+
if (!((typeof value === 'string' && /^\d{4}$/.test(value)) ||
|
|
327
|
+
(typeof value === 'number' && Number.isInteger(value)))) {
|
|
328
|
+
return 'YEAR requires YYYY value';
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (operator === 'monthBetween') {
|
|
332
|
+
if (!Array.isArray(value) || value.length !== 2) {
|
|
333
|
+
return 'MONTH BETWEEN requires array of 2 YYYY-MM values';
|
|
334
|
+
}
|
|
335
|
+
const [from, to] = value;
|
|
336
|
+
if (typeof from !== 'string' || !/^\d{4}-\d{2}$/.test(from) ||
|
|
337
|
+
typeof to !== 'string' || !/^\d{4}-\d{2}$/.test(to)) {
|
|
338
|
+
return 'MONTH BETWEEN requires array of 2 YYYY-MM values';
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (operator === 'yearBetween') {
|
|
342
|
+
if (!Array.isArray(value) || value.length !== 2) {
|
|
343
|
+
return 'YEAR BETWEEN requires array of 2 YYYY values';
|
|
344
|
+
}
|
|
345
|
+
const [from, to] = value;
|
|
346
|
+
const isValidYear = (v) => (typeof v === 'string' && /^\d{4}$/.test(v)) ||
|
|
347
|
+
(typeof v === 'number' && Number.isInteger(v));
|
|
348
|
+
if (!isValidYear(from) || !isValidYear(to)) {
|
|
349
|
+
return 'YEAR BETWEEN requires array of 2 YYYY values';
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// in/notIn require array
|
|
353
|
+
if (operator === 'in' || operator === 'notIn') {
|
|
354
|
+
if (!Array.isArray(value)) {
|
|
355
|
+
return 'IN/NOT IN requires array of values';
|
|
356
|
+
}
|
|
357
|
+
if (value.length === 0) {
|
|
358
|
+
return 'IN/NOT IN requires at least one value';
|
|
359
|
+
}
|
|
360
|
+
if (value.length > FILTER_SECURITY_LIMITS.MAX_IN_VALUES) {
|
|
361
|
+
// Note: We can't call logSecurityEvent from validateValue since it returns string
|
|
362
|
+
// Security logging for this is handled in validateCondition
|
|
363
|
+
return `Too many values (max ${FILTER_SECURITY_LIMITS.MAX_IN_VALUES})`;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
// regex requires valid pattern
|
|
367
|
+
if (operator === 'regex') {
|
|
368
|
+
if (typeof value !== 'string') {
|
|
369
|
+
return 'Regex requires string pattern';
|
|
370
|
+
}
|
|
371
|
+
try {
|
|
372
|
+
new RegExp(value);
|
|
373
|
+
}
|
|
374
|
+
catch (e) {
|
|
375
|
+
return `Invalid regex pattern: ${e?.message || e}`;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
// String length check
|
|
379
|
+
if (typeof value === 'string') {
|
|
380
|
+
if (value.length > FILTER_SECURITY_LIMITS.MAX_STRING_LENGTH) {
|
|
381
|
+
return `String too long (max ${FILTER_SECURITY_LIMITS.MAX_STRING_LENGTH} characters)`;
|
|
382
|
+
}
|
|
383
|
+
if (fieldConfig.maxLength && value.length > fieldConfig.maxLength) {
|
|
384
|
+
return `String exceeds field max length of ${fieldConfig.maxLength}`;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
// Number range check
|
|
388
|
+
if (typeof value === 'number') {
|
|
389
|
+
if (fieldConfig.min !== undefined && value < fieldConfig.min) {
|
|
390
|
+
return `Value must be at least ${fieldConfig.min}`;
|
|
391
|
+
}
|
|
392
|
+
if (fieldConfig.max !== undefined && value > fieldConfig.max) {
|
|
393
|
+
return `Value must be at most ${fieldConfig.max}`;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
// Type validation for specific field types
|
|
397
|
+
if (fieldConfig.type === 'boolean') {
|
|
398
|
+
if (typeof value !== 'boolean' && value !== 'true' && value !== 'false') {
|
|
399
|
+
return 'Value must be boolean';
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (fieldConfig.type === 'number' || fieldConfig.type === 'integer' || fieldConfig.type === 'float') {
|
|
403
|
+
if (typeof value !== 'number' && isNaN(parseFloat(value))) {
|
|
404
|
+
return 'Value must be a number';
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Validate raw SQL for obvious injection attempts
|
|
411
|
+
*/
|
|
412
|
+
validateRawSQL(condition, errors) {
|
|
413
|
+
const sql = condition.rawSQL;
|
|
414
|
+
if (!sql)
|
|
415
|
+
return;
|
|
416
|
+
// Check for dangerous patterns
|
|
417
|
+
const dangerousPatterns = [
|
|
418
|
+
/;\s*drop\s+/i,
|
|
419
|
+
/;\s*delete\s+/i,
|
|
420
|
+
/;\s*update\s+/i,
|
|
421
|
+
/;\s*insert\s+/i,
|
|
422
|
+
/;\s*alter\s+/i,
|
|
423
|
+
/;\s*create\s+/i,
|
|
424
|
+
/;\s*truncate\s+/i,
|
|
425
|
+
/union\s+select/i,
|
|
426
|
+
/--\s*$/m,
|
|
427
|
+
/\/\*.*\*\//s
|
|
428
|
+
];
|
|
429
|
+
for (const pattern of dangerousPatterns) {
|
|
430
|
+
if (pattern.test(sql)) {
|
|
431
|
+
this.logSecurityEvent('DANGEROUS_SQL', condition.id, `SQL injection attempt detected: pattern ${pattern.toString()} matched`, sql.substring(0, 200));
|
|
432
|
+
errors.push({
|
|
433
|
+
conditionId: condition.id,
|
|
434
|
+
message: 'Raw SQL contains potentially dangerous pattern',
|
|
435
|
+
code: 'DANGEROUS_SQL'
|
|
436
|
+
});
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
// Check that params placeholders match params count
|
|
441
|
+
if (condition.rawSQLParams) {
|
|
442
|
+
const placeholderCount = (sql.match(/\$\d+|\?/g) || []).length;
|
|
443
|
+
if (placeholderCount !== condition.rawSQLParams.length) {
|
|
444
|
+
errors.push({
|
|
445
|
+
conditionId: condition.id,
|
|
446
|
+
message: `Placeholder count (${placeholderCount}) doesn't match params count (${condition.rawSQLParams.length})`,
|
|
447
|
+
code: 'PARAM_MISMATCH'
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Get list of allowed operators for a field
|
|
454
|
+
*/
|
|
455
|
+
getOperatorsForField(fieldName) {
|
|
456
|
+
const fieldConfig = this.fieldsConfig[fieldName];
|
|
457
|
+
if (!fieldConfig) {
|
|
458
|
+
return [];
|
|
459
|
+
}
|
|
460
|
+
return this.getOperatorsForType(fieldConfig.type);
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Get list of allowed operators for a field type
|
|
464
|
+
*/
|
|
465
|
+
getOperatorsForType(fieldType) {
|
|
466
|
+
const operatorsByType = {
|
|
467
|
+
string: ['eq', 'neq', 'like', 'ilike', 'startsWith', 'endsWith', 'in', 'notIn', 'isNull', 'isNotNull', 'regex'],
|
|
468
|
+
text: ['eq', 'neq', 'like', 'ilike', 'startsWith', 'endsWith', 'isNull', 'isNotNull'],
|
|
469
|
+
number: ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'between', 'in', 'notIn', 'isNull', 'isNotNull'],
|
|
470
|
+
integer: ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'between', 'in', 'notIn', 'isNull', 'isNotNull'],
|
|
471
|
+
float: ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'between', 'in', 'notIn', 'isNull', 'isNotNull'],
|
|
472
|
+
boolean: ['eq', 'neq', 'isNull', 'isNotNull'],
|
|
473
|
+
date: ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'between', 'today', 'month', 'year', 'monthBetween', 'yearBetween', 'isNull', 'isNotNull'],
|
|
474
|
+
datetime: ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'between', 'today', 'month', 'year', 'monthBetween', 'yearBetween', 'isNull', 'isNotNull'],
|
|
475
|
+
select: ['eq', 'neq', 'in', 'notIn', 'isNull', 'isNotNull'],
|
|
476
|
+
json: ['isNull', 'isNotNull', 'custom'],
|
|
477
|
+
association: ['eq', 'neq', 'in', 'notIn', 'isNull', 'isNotNull'],
|
|
478
|
+
'association-many': ['in', 'notIn', 'isNull', 'isNotNull']
|
|
479
|
+
};
|
|
480
|
+
return operatorsByType[fieldType] || operatorsByType.string;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
export default ConditionValidator;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom field condition result
|
|
3
|
+
*/
|
|
4
|
+
export interface FilterCustomFieldCondition {
|
|
5
|
+
rawSQL?: string;
|
|
6
|
+
params?: any[];
|
|
7
|
+
inMemory?: (record: any) => boolean;
|
|
8
|
+
criteria?: Record<string, any>;
|
|
9
|
+
}
|
|
10
|
+
export type FilterCustomFieldInputType = "text" | "number";
|
|
11
|
+
export interface FilterCustomFieldInputConfigItem {
|
|
12
|
+
placeholder: string;
|
|
13
|
+
type?: FilterCustomFieldInputType;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Input config provided by a custom filter handler class.
|
|
17
|
+
* Keys are input IDs; values describe placeholders and optional HTML input type.
|
|
18
|
+
*/
|
|
19
|
+
export type FilterCustomFieldInputConfig = Record<string, FilterCustomFieldInputConfigItem>;
|
|
20
|
+
export interface FilterCustomFieldInputDefinition {
|
|
21
|
+
id: string;
|
|
22
|
+
placeholder: string;
|
|
23
|
+
type: FilterCustomFieldInputType;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Base custom filter handler contract.
|
|
27
|
+
* Stores model and field separately and builds a full handler ID as "Model.field".
|
|
28
|
+
*/
|
|
29
|
+
export declare abstract class AbstractCustomFilter {
|
|
30
|
+
private readonly modelName;
|
|
31
|
+
private readonly fieldName;
|
|
32
|
+
protected constructor(modelName: string, fieldName: string);
|
|
33
|
+
get id(): string;
|
|
34
|
+
/**
|
|
35
|
+
* Handler name for display
|
|
36
|
+
*/
|
|
37
|
+
abstract readonly name: string;
|
|
38
|
+
/**
|
|
39
|
+
* Handler description
|
|
40
|
+
*/
|
|
41
|
+
readonly description?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Optional input config for UI (up to 3 inputs).
|
|
44
|
+
* Example:
|
|
45
|
+
* {
|
|
46
|
+
* from: { placeholder: "From", type: "number" },
|
|
47
|
+
* to: { placeholder: "To", type: "number" }
|
|
48
|
+
* }
|
|
49
|
+
*/
|
|
50
|
+
readonly inputConfig?: FilterCustomFieldInputConfig;
|
|
51
|
+
/**
|
|
52
|
+
* Build condition for the custom field
|
|
53
|
+
* @param value - Filter value
|
|
54
|
+
* @param params - Additional parameters for the handler
|
|
55
|
+
*/
|
|
56
|
+
abstract buildCondition(value: any, params?: any): FilterCustomFieldCondition;
|
|
57
|
+
/**
|
|
58
|
+
* Validate value before applying filter
|
|
59
|
+
*/
|
|
60
|
+
validate?(value: any): {
|
|
61
|
+
valid: boolean;
|
|
62
|
+
error?: string;
|
|
63
|
+
};
|
|
64
|
+
getInputDefinitions(): FilterCustomFieldInputDefinition[];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Registry options
|
|
68
|
+
*/
|
|
69
|
+
export interface RegisterOptions {
|
|
70
|
+
/**
|
|
71
|
+
* Force overwrite existing handler
|
|
72
|
+
*/
|
|
73
|
+
force?: boolean;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* CustomFilterHandler - registry for custom field handlers
|
|
77
|
+
*/
|
|
78
|
+
export declare class CustomFilterHandler {
|
|
79
|
+
private handlers;
|
|
80
|
+
private validateInputConfig;
|
|
81
|
+
/**
|
|
82
|
+
* Register a custom handler
|
|
83
|
+
* @param handler - Handler instance
|
|
84
|
+
* @param options - Registration options
|
|
85
|
+
*/
|
|
86
|
+
add(handler: AbstractCustomFilter, options?: RegisterOptions): void;
|
|
87
|
+
/**
|
|
88
|
+
* Alias for add()
|
|
89
|
+
*/
|
|
90
|
+
register(handler: AbstractCustomFilter, options?: RegisterOptions): void;
|
|
91
|
+
/**
|
|
92
|
+
* Get handler by ID
|
|
93
|
+
*/
|
|
94
|
+
get(id: string): AbstractCustomFilter | undefined;
|
|
95
|
+
/**
|
|
96
|
+
* Check if handler exists
|
|
97
|
+
*/
|
|
98
|
+
has(id: string): boolean;
|
|
99
|
+
/**
|
|
100
|
+
* Get all handlers
|
|
101
|
+
*/
|
|
102
|
+
getAll(): Map<string, AbstractCustomFilter>;
|
|
103
|
+
/**
|
|
104
|
+
* Get handlers for a specific model
|
|
105
|
+
* @param modelName - Model name (e.g., "Order")
|
|
106
|
+
* @returns Map of field handlers for the model
|
|
107
|
+
*/
|
|
108
|
+
getForModel(modelName: string): Map<string, AbstractCustomFilter>;
|
|
109
|
+
/**
|
|
110
|
+
* Unregister a handler
|
|
111
|
+
*/
|
|
112
|
+
unregister(id: string): boolean;
|
|
113
|
+
/**
|
|
114
|
+
* Clear all handlers (useful for testing)
|
|
115
|
+
*/
|
|
116
|
+
clear(): void;
|
|
117
|
+
/**
|
|
118
|
+
* Get count of registered handlers
|
|
119
|
+
*/
|
|
120
|
+
count(): number;
|
|
121
|
+
}
|
|
122
|
+
export default CustomFilterHandler;
|