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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DataTypes, Op } from "sequelize";
|
|
1
|
+
import { Sequelize, DataTypes, Op, HasMany } from "sequelize";
|
|
2
2
|
import { AbstractAdapter, AbstractModel } from "../AbstractModel.js";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import fs from "fs";
|
|
@@ -64,10 +64,15 @@ function generateAssociationsFromSchema(models, schemas) {
|
|
|
64
64
|
if (model.rawAttributes[alias] || model.associations[alias]) {
|
|
65
65
|
asName = `${alias}Ref`;
|
|
66
66
|
}
|
|
67
|
-
|
|
67
|
+
const belongsToOptions = {
|
|
68
68
|
as: asName,
|
|
69
69
|
foreignKey,
|
|
70
|
-
}
|
|
70
|
+
};
|
|
71
|
+
// Add onDelete constraint if specified
|
|
72
|
+
if (field.onDelete) {
|
|
73
|
+
belongsToOptions.onDelete = field.onDelete;
|
|
74
|
+
}
|
|
75
|
+
model.belongsTo(targetModel, belongsToOptions);
|
|
71
76
|
}
|
|
72
77
|
}
|
|
73
78
|
}
|
|
@@ -76,19 +81,24 @@ function resolveType(type) {
|
|
|
76
81
|
const sqlType = typeof type.toString === "function"
|
|
77
82
|
? type.toString().toLowerCase()
|
|
78
83
|
: "";
|
|
79
|
-
if (sqlType.includes("string") ||
|
|
84
|
+
if (sqlType.includes("string") ||
|
|
85
|
+
sqlType.includes("uuid") ||
|
|
86
|
+
sqlType.includes("char") ||
|
|
87
|
+
sqlType.includes("text")) {
|
|
80
88
|
return "string";
|
|
89
|
+
}
|
|
90
|
+
// tinyint(1) is used by MySQL/MariaDB for BOOLEAN
|
|
91
|
+
if (sqlType.includes("bool") || sqlType === "tinyint(1)")
|
|
92
|
+
return "boolean";
|
|
81
93
|
if (sqlType.includes("int") || sqlType.includes("float") || sqlType.includes("decimal"))
|
|
82
94
|
return "number";
|
|
83
|
-
if (sqlType.includes("bool"))
|
|
84
|
-
return "boolean";
|
|
85
95
|
if (sqlType.includes("json"))
|
|
86
96
|
return "json";
|
|
87
97
|
if (sqlType.includes("date"))
|
|
88
98
|
return "string";
|
|
89
99
|
return "ref";
|
|
90
100
|
}
|
|
91
|
-
export function
|
|
101
|
+
export function mapSequelizeToAdminizerAttributes(model) {
|
|
92
102
|
const result = {};
|
|
93
103
|
const rawAttrs = model.getAttributes();
|
|
94
104
|
for (const name in rawAttrs) {
|
|
@@ -155,61 +165,233 @@ function capitalize(str) {
|
|
|
155
165
|
export class SequelizeModel extends AbstractModel {
|
|
156
166
|
model;
|
|
157
167
|
constructor(modelName, model) {
|
|
158
|
-
super(modelName,
|
|
168
|
+
super(modelName, mapSequelizeToAdminizerAttributes(model), model.primaryKeyAttribute, model.name);
|
|
159
169
|
this.model = model;
|
|
160
170
|
}
|
|
171
|
+
_buildJsonContainsCondition(targetKey, item) {
|
|
172
|
+
const pattern = `%${JSON.stringify(item).replace(/[%_]/g, "\\$&")}%`;
|
|
173
|
+
const attribute = this.model.rawAttributes?.[targetKey];
|
|
174
|
+
const columnName = typeof attribute?.field === "string" ? attribute.field : targetKey;
|
|
175
|
+
return Sequelize.where(Sequelize.cast(Sequelize.col(`${this.model.name}.${columnName}`), "TEXT"), { [Op.like]: pattern });
|
|
176
|
+
}
|
|
161
177
|
_convertCriteriaToSequelize(criteria) {
|
|
178
|
+
// Special handling for Sequelize.literal() at the top level
|
|
179
|
+
if (criteria && typeof criteria === 'object' && 'val' in criteria && typeof criteria.val === 'string') {
|
|
180
|
+
// This is a Sequelize.literal object, return it as-is
|
|
181
|
+
return criteria;
|
|
182
|
+
}
|
|
162
183
|
const result = {};
|
|
163
|
-
|
|
184
|
+
// Handle both string keys and Symbol keys (Op.and, Op.or, etc.)
|
|
185
|
+
const allKeys = [
|
|
186
|
+
...Object.keys(criteria),
|
|
187
|
+
...Object.getOwnPropertySymbols(criteria)
|
|
188
|
+
];
|
|
189
|
+
for (const key of allKeys) {
|
|
164
190
|
const value = criteria[key];
|
|
165
|
-
if (
|
|
166
|
-
(typeof value === "object" && value !== null && Object.keys(value).length === 0)) {
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
// 🧠 Replace the key with `via` if this is an association
|
|
170
|
-
const attr = this.attributes?.[key];
|
|
191
|
+
// 🧠 Replace the key with `via` if this is an association (MUST happen before null/other checks)
|
|
171
192
|
let targetKey = key;
|
|
172
|
-
|
|
173
|
-
|
|
193
|
+
// Only process string keys for association replacement
|
|
194
|
+
if (typeof key === 'string') {
|
|
195
|
+
const attr = this.attributes?.[key];
|
|
196
|
+
// Check if this is an association attribute (either from attributes or model.associations)
|
|
197
|
+
if (attr?.type === "association" && attr.via) {
|
|
198
|
+
targetKey = attr.via;
|
|
199
|
+
}
|
|
200
|
+
else if (this.model.associations[key]) {
|
|
201
|
+
// Fallback: check associations directly
|
|
202
|
+
const assoc = this.model.associations[key];
|
|
203
|
+
if (assoc && 'foreignKey' in assoc) {
|
|
204
|
+
targetKey = assoc.foreignKey;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
174
207
|
}
|
|
208
|
+
// Special handling for null values (IS NULL condition)
|
|
175
209
|
if (value === null) {
|
|
176
210
|
result[targetKey] = { [Op.is]: null };
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
// 🔹 Handle Sequelize Op operators (already in Sequelize format)
|
|
214
|
+
if (key === Op.and || key === 'and') {
|
|
215
|
+
result[Op.and] = Array.isArray(value)
|
|
216
|
+
? value.map((item) => this._convertCriteriaToSequelize(item))
|
|
217
|
+
: [];
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (key === Op.or || key === 'or') {
|
|
221
|
+
result[Op.or] = Array.isArray(value)
|
|
222
|
+
? value.map((item) => this._convertCriteriaToSequelize(item))
|
|
223
|
+
: [];
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (key === Op.not || key === 'not' || key === '$not') {
|
|
227
|
+
result[Op.not] = this._convertCriteriaToSequelize(value);
|
|
228
|
+
continue;
|
|
177
229
|
}
|
|
178
|
-
|
|
230
|
+
// Skip Symbol keys that are not handled above (should not happen, but safety check)
|
|
231
|
+
if (typeof key === 'symbol') {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (Array.isArray(value)) {
|
|
179
235
|
// ✅ Array processing - use the IN operator
|
|
180
236
|
result[targetKey] = { [Op.in]: value };
|
|
181
237
|
}
|
|
182
238
|
else if (typeof value === "object" && !Array.isArray(value)) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
239
|
+
// Check if value already contains Sequelize Op operators
|
|
240
|
+
// Need to check both string keys and Symbol keys
|
|
241
|
+
const valueKeys = Object.keys(value);
|
|
242
|
+
const valueSymbols = Object.getOwnPropertySymbols(value);
|
|
243
|
+
// Helper function to check if a symbol/op is a Sequelize operator
|
|
244
|
+
const isSequelizeOp = (op) => {
|
|
245
|
+
// Check direct Symbol equality first
|
|
246
|
+
if (op === Op.gt || op === Op.gte || op === Op.lt || op === Op.lte ||
|
|
247
|
+
op === Op.eq || op === Op.ne || op === Op.in || op === Op.notIn ||
|
|
248
|
+
op === Op.like || op === Op.iLike || op === Op.between ||
|
|
249
|
+
op === Op.startsWith || op === Op.endsWith || op === Op.is || op === Op.not) {
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
// Fallback: check by Symbol description (handles different Symbol instances)
|
|
253
|
+
if (typeof op === 'symbol') {
|
|
254
|
+
const desc = op.description || op.toString();
|
|
255
|
+
return desc.includes('gt') || desc.includes('gte') || desc.includes('lt') || desc.includes('lte') ||
|
|
256
|
+
desc.includes('eq') || desc.includes('ne') || desc.includes('in') || desc.includes('notIn') ||
|
|
257
|
+
desc.includes('like') || desc.includes('iLike') || desc.includes('between') ||
|
|
258
|
+
desc.includes('startsWith') || desc.includes('endsWith') || desc.includes('is') || desc.includes('not');
|
|
259
|
+
}
|
|
260
|
+
// Check string operators used by Adminizer's adapter-neutral criteria format
|
|
261
|
+
return op === '$gt' || op === '$gte' || op === '$lt' || op === '$lte' ||
|
|
262
|
+
op === '$eq' || op === '$ne' || op === '$in' || op === '$notIn' ||
|
|
263
|
+
op === '$like' || op === '$iLike' || op === '$between' ||
|
|
264
|
+
op === '$startsWith' || op === '$endsWith' || op === '$is' || op === '$not' ||
|
|
265
|
+
op === 'not';
|
|
266
|
+
};
|
|
267
|
+
const hasSequelizeOp = [...valueKeys, ...valueSymbols].some(isSequelizeOp);
|
|
268
|
+
// If already Sequelize format, pass through
|
|
269
|
+
if (hasSequelizeOp) {
|
|
270
|
+
result[targetKey] = value;
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
// Handle operator objects like {contains: 'val'} from Adminizer's criteria format
|
|
274
|
+
for (const [op, val] of Object.entries(value)) {
|
|
275
|
+
if (val === undefined || val === null)
|
|
276
|
+
continue;
|
|
186
277
|
switch (op) {
|
|
278
|
+
case "eq":
|
|
279
|
+
result[targetKey] = { [Op.eq]: val };
|
|
280
|
+
break;
|
|
281
|
+
case "ne":
|
|
282
|
+
result[targetKey] = { [Op.ne]: val };
|
|
283
|
+
break;
|
|
284
|
+
case "gt":
|
|
285
|
+
result[targetKey] = { [Op.gt]: val };
|
|
286
|
+
break;
|
|
287
|
+
case "gte":
|
|
288
|
+
result[targetKey] = { [Op.gte]: val };
|
|
289
|
+
break;
|
|
290
|
+
case "lt":
|
|
291
|
+
result[targetKey] = { [Op.lt]: val };
|
|
292
|
+
break;
|
|
293
|
+
case "lte":
|
|
294
|
+
result[targetKey] = { [Op.lte]: val };
|
|
295
|
+
break;
|
|
187
296
|
case "contains":
|
|
188
|
-
|
|
297
|
+
// Check if field is a date/datetime type
|
|
298
|
+
const attr = this.model.rawAttributes?.[targetKey];
|
|
299
|
+
const typeName = attr?.type;
|
|
300
|
+
// Check type name (works for DATE, DATEONLY, TIME, etc.)
|
|
301
|
+
const typeStr = typeof typeName === 'function'
|
|
302
|
+
? typeName.name.toLowerCase()
|
|
303
|
+
: String(typeName).toLowerCase();
|
|
304
|
+
const isDateType = typeStr.includes('date') || typeStr.includes('time');
|
|
305
|
+
// TIME type should use LIKE (it's stored as 'HH:MM' string)
|
|
306
|
+
const isTimeType = typeStr === 'time';
|
|
307
|
+
if (isDateType && !isTimeType) {
|
|
308
|
+
// For date fields (not time), check if value is a valid date
|
|
309
|
+
const dateValue = new Date(String(val));
|
|
310
|
+
if (isNaN(dateValue.getTime())) {
|
|
311
|
+
// Invalid date string - skip this condition entirely
|
|
312
|
+
// This prevents moment.js warnings
|
|
313
|
+
// Don't add anything to result - field will be excluded from WHERE
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
// Valid date - use exact match
|
|
317
|
+
result[targetKey] = { [Op.eq]: dateValue };
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
// For time fields and non-date fields - use Op.like with wildcards
|
|
322
|
+
result[targetKey] = { [Op.like]: `%${val}%` };
|
|
323
|
+
}
|
|
324
|
+
break;
|
|
189
325
|
case "startsWith":
|
|
190
|
-
|
|
326
|
+
result[targetKey] = { [Op.startsWith]: val };
|
|
327
|
+
break;
|
|
191
328
|
case "endsWith":
|
|
192
|
-
|
|
329
|
+
result[targetKey] = { [Op.endsWith]: val };
|
|
330
|
+
break;
|
|
193
331
|
case ">":
|
|
194
|
-
|
|
332
|
+
result[targetKey] = { [Op.gt]: val };
|
|
333
|
+
break;
|
|
195
334
|
case ">=":
|
|
196
|
-
|
|
335
|
+
result[targetKey] = { [Op.gte]: val };
|
|
336
|
+
break;
|
|
197
337
|
case "<":
|
|
198
|
-
|
|
338
|
+
result[targetKey] = { [Op.lt]: val };
|
|
339
|
+
break;
|
|
199
340
|
case "<=":
|
|
200
|
-
|
|
341
|
+
result[targetKey] = { [Op.lte]: val };
|
|
342
|
+
break;
|
|
201
343
|
case "!=":
|
|
202
|
-
|
|
344
|
+
result[targetKey] = { [Op.ne]: val };
|
|
345
|
+
break;
|
|
203
346
|
case "in":
|
|
204
|
-
|
|
347
|
+
result[targetKey] = { [Op.in]: val };
|
|
348
|
+
break;
|
|
349
|
+
case "notIn":
|
|
350
|
+
result[targetKey] = { [Op.notIn]: val };
|
|
351
|
+
break;
|
|
205
352
|
case "nin":
|
|
206
|
-
|
|
353
|
+
result[targetKey] = { [Op.notIn]: val };
|
|
354
|
+
break;
|
|
355
|
+
case "between":
|
|
356
|
+
result[targetKey] = { [Op.between]: val };
|
|
357
|
+
break;
|
|
358
|
+
case "isNull":
|
|
359
|
+
if (val) {
|
|
360
|
+
result[targetKey] = { [Op.is]: null };
|
|
361
|
+
}
|
|
362
|
+
break;
|
|
363
|
+
case "isNotNull":
|
|
364
|
+
if (val) {
|
|
365
|
+
result[targetKey] = { [Op.not]: null };
|
|
366
|
+
}
|
|
367
|
+
break;
|
|
368
|
+
case "regex":
|
|
369
|
+
result[targetKey] = { [Op.regexp]: val };
|
|
370
|
+
break;
|
|
371
|
+
case "jsonContains": {
|
|
372
|
+
const values = Array.isArray(val) ? val : [val];
|
|
373
|
+
const clauses = values.map((item) => this._buildJsonContainsCondition(targetKey, item));
|
|
374
|
+
result[Op.and] = [
|
|
375
|
+
...(result[Op.and] ?? []),
|
|
376
|
+
...clauses
|
|
377
|
+
];
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
case "$is":
|
|
381
|
+
result[targetKey] = { [Op.is]: val };
|
|
382
|
+
break;
|
|
383
|
+
case "$not":
|
|
384
|
+
result[targetKey] = { [Op.not]: val };
|
|
385
|
+
break;
|
|
386
|
+
case "$ne":
|
|
387
|
+
result[targetKey] = { [Op.ne]: val };
|
|
388
|
+
break;
|
|
389
|
+
case "not":
|
|
390
|
+
result[targetKey] = { [Op.not]: val };
|
|
391
|
+
break;
|
|
207
392
|
default:
|
|
208
|
-
|
|
393
|
+
result[targetKey] = { [Op.eq]: val };
|
|
209
394
|
}
|
|
210
|
-
});
|
|
211
|
-
if (operatorEntries.length > 0) {
|
|
212
|
-
result[targetKey] = Object.fromEntries(operatorEntries);
|
|
213
395
|
}
|
|
214
396
|
}
|
|
215
397
|
else {
|
|
@@ -218,13 +400,23 @@ export class SequelizeModel extends AbstractModel {
|
|
|
218
400
|
}
|
|
219
401
|
return result;
|
|
220
402
|
}
|
|
221
|
-
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
403
|
+
_convertAdminizerCriteriaToSequelizeOptions(criteria) {
|
|
404
|
+
// For Sequelize: extract adapter-neutral pagination and ordering params.
|
|
405
|
+
// Everything else is WHERE criteria.
|
|
406
|
+
// 'sort' as string = ordering ("field direction") — goes to ORDER BY
|
|
407
|
+
// 'sort' as boolean/object = model field — goes to WHERE
|
|
408
|
+
const criteriaSortValue = 'sort' in criteria ? criteria.sort : undefined;
|
|
409
|
+
const isOrderingSort = typeof criteriaSortValue === 'string';
|
|
410
|
+
const { where: nestedWhere, skip, limit, sort: criteriaSort, select, populate, ...rest } = criteria;
|
|
411
|
+
// If 'sort' is a model field (not an ordering string), put it back
|
|
412
|
+
if (!isOrderingSort && criteriaSortValue !== undefined) {
|
|
413
|
+
rest.sort = criteriaSortValue;
|
|
414
|
+
}
|
|
415
|
+
// Check if nestedWhere has any keys (including Symbol keys like Op.and)
|
|
416
|
+
const hasNestedWhereKeys = nestedWhere && (Object.keys(nestedWhere).length > 0 ||
|
|
417
|
+
Object.getOwnPropertySymbols(nestedWhere).length > 0);
|
|
418
|
+
// If there's an explicit 'where' key, use it. Otherwise use rest (field conditions).
|
|
419
|
+
const rawWhere = hasNestedWhereKeys ? nestedWhere : rest;
|
|
228
420
|
const where = this._convertCriteriaToSequelize(rawWhere);
|
|
229
421
|
const result = { where };
|
|
230
422
|
if (typeof skip === "number") {
|
|
@@ -235,11 +427,21 @@ export class SequelizeModel extends AbstractModel {
|
|
|
235
427
|
result.limit = limit;
|
|
236
428
|
// console.debug("→ limit =", limit);
|
|
237
429
|
}
|
|
238
|
-
if (typeof
|
|
239
|
-
const [field, dir] =
|
|
430
|
+
if (typeof criteriaSort === "string") {
|
|
431
|
+
const [field, dir] = criteriaSort.trim().split(/\s+/);
|
|
240
432
|
result.order = [[field, dir?.toUpperCase() === "DESC" ? "DESC" : "ASC"]];
|
|
241
433
|
// console.debug("→ order =", result.order);
|
|
242
434
|
}
|
|
435
|
+
else if (criteriaSort && typeof criteriaSort === "object") {
|
|
436
|
+
result.order = Object.entries(criteriaSort).map(([field, dir]) => [
|
|
437
|
+
field,
|
|
438
|
+
String(dir).toUpperCase() === "DESC" ? "DESC" : "ASC"
|
|
439
|
+
]);
|
|
440
|
+
}
|
|
441
|
+
const attributes = this._buildAttributes(select);
|
|
442
|
+
if (attributes) {
|
|
443
|
+
result.attributes = attributes;
|
|
444
|
+
}
|
|
243
445
|
return result;
|
|
244
446
|
}
|
|
245
447
|
async _assignAssociations(instance, assocData) {
|
|
@@ -293,8 +495,6 @@ export class SequelizeModel extends AbstractModel {
|
|
|
293
495
|
plainData[key] = val;
|
|
294
496
|
}
|
|
295
497
|
}
|
|
296
|
-
console.debug(">> Normal fields for create():", JSON.stringify(plainData));
|
|
297
|
-
console.debug(">> Association data:", JSON.stringify(assocData));
|
|
298
498
|
let instance;
|
|
299
499
|
try {
|
|
300
500
|
instance = await this.model.create(plainData);
|
|
@@ -345,13 +545,15 @@ export class SequelizeModel extends AbstractModel {
|
|
|
345
545
|
// --- FIND ONE ---
|
|
346
546
|
async _findOne(criteria) {
|
|
347
547
|
// console.debug(">> _findOne: input criteria:", criteria);
|
|
348
|
-
const { where } = this.
|
|
349
|
-
const includes =
|
|
548
|
+
const { where, attributes } = this._convertAdminizerCriteriaToSequelizeOptions(criteria);
|
|
549
|
+
const includes = criteria.populate
|
|
550
|
+
? this._buildCriteriaIncludes(criteria.populate)
|
|
551
|
+
: this._buildIncludes();
|
|
350
552
|
// console.debug(">> _findOne: converted where:", where);
|
|
351
553
|
// console.debug(">> _findOne: includes:", includes);
|
|
352
554
|
let instance = null;
|
|
353
555
|
try {
|
|
354
|
-
instance = await this.model.findOne({ where, include: includes });
|
|
556
|
+
instance = await this.model.findOne({ where, attributes, include: includes });
|
|
355
557
|
// console.debug(">> _findOne: raw instance:", instance ? instance.toJSON() : null);
|
|
356
558
|
}
|
|
357
559
|
catch (err) {
|
|
@@ -367,13 +569,17 @@ export class SequelizeModel extends AbstractModel {
|
|
|
367
569
|
return plain;
|
|
368
570
|
}
|
|
369
571
|
// --- FIND MANY ---
|
|
370
|
-
async _find(criteria = {}
|
|
572
|
+
async _find(criteria = {}) {
|
|
371
573
|
const assocNames = Object.keys(this.model.associations);
|
|
372
574
|
// console.debug(">> _find: input criteria:", criteria, "options:", options);
|
|
373
|
-
const { where, limit, offset, order } = this.
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
575
|
+
const { where, limit, offset, order, attributes } = this._convertAdminizerCriteriaToSequelizeOptions(criteria);
|
|
576
|
+
const hasRelationPathCondition = this._hasRelationPathCondition(where);
|
|
577
|
+
const usedRelationAliases = hasRelationPathCondition
|
|
578
|
+
? this._extractRelationAliasesFromWhere(where)
|
|
579
|
+
: new Set();
|
|
580
|
+
const includes = criteria.populate
|
|
581
|
+
? this._buildCriteriaIncludes(criteria.populate)
|
|
582
|
+
: assocNames.map((alias) => this._buildListInclude(alias, usedRelationAliases, hasRelationPathCondition));
|
|
377
583
|
// console.debug(">> _find: where, limit, offset, order, includes:", {
|
|
378
584
|
// where,
|
|
379
585
|
// limit,
|
|
@@ -384,10 +590,12 @@ export class SequelizeModel extends AbstractModel {
|
|
|
384
590
|
let instances;
|
|
385
591
|
instances = await this.model.findAll({
|
|
386
592
|
where,
|
|
593
|
+
attributes,
|
|
387
594
|
limit,
|
|
388
595
|
offset,
|
|
389
596
|
order,
|
|
390
|
-
include: includes
|
|
597
|
+
include: includes,
|
|
598
|
+
subQuery: hasRelationPathCondition ? false : undefined
|
|
391
599
|
});
|
|
392
600
|
for (const inst of instances) {
|
|
393
601
|
//For each association, we call getxxx () once again
|
|
@@ -412,9 +620,40 @@ export class SequelizeModel extends AbstractModel {
|
|
|
412
620
|
// console.debug(">> _find: plain results:", plain);
|
|
413
621
|
return plain;
|
|
414
622
|
}
|
|
623
|
+
/**
|
|
624
|
+
* Public method for raw SQL where clauses (Sequelize.literal support)
|
|
625
|
+
* Bypasses Adminizer criteria conversion for direct Sequelize usage
|
|
626
|
+
*/
|
|
627
|
+
async findWithRawWhere(where, options = {}) {
|
|
628
|
+
const assocNames = Object.keys(this.model.associations);
|
|
629
|
+
const includes = options.populate !== false
|
|
630
|
+
? assocNames.map(a => ({ association: a }))
|
|
631
|
+
: [];
|
|
632
|
+
const instances = await this.model.findAll({
|
|
633
|
+
where,
|
|
634
|
+
limit: options.limit,
|
|
635
|
+
offset: options.offset,
|
|
636
|
+
order: options.order,
|
|
637
|
+
include: includes
|
|
638
|
+
});
|
|
639
|
+
return instances.map(i => i.get({ plain: true }));
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Public method for count with raw SQL where clauses
|
|
643
|
+
*/
|
|
644
|
+
async countWithRawWhere(where) {
|
|
645
|
+
const assocNames = Object.keys(this.model.associations);
|
|
646
|
+
const include = assocNames.map((association) => ({ association }));
|
|
647
|
+
return await this.model.count({
|
|
648
|
+
where,
|
|
649
|
+
include,
|
|
650
|
+
distinct: true,
|
|
651
|
+
col: this.primaryKey || this.model.primaryKeyAttribute || 'id'
|
|
652
|
+
});
|
|
653
|
+
}
|
|
415
654
|
// --- UPDATE ONE ---
|
|
416
655
|
async _updateOne(criteria, data) {
|
|
417
|
-
const { where } = this.
|
|
656
|
+
const { where } = this._convertAdminizerCriteriaToSequelizeOptions(criteria);
|
|
418
657
|
const record = await this.model.findOne({ where });
|
|
419
658
|
if (!record)
|
|
420
659
|
return null;
|
|
@@ -445,7 +684,7 @@ export class SequelizeModel extends AbstractModel {
|
|
|
445
684
|
}
|
|
446
685
|
// --- UPDATE MANY ---
|
|
447
686
|
async _update(criteria, data) {
|
|
448
|
-
const { where } = this.
|
|
687
|
+
const { where } = this._convertAdminizerCriteriaToSequelizeOptions(criteria);
|
|
449
688
|
const assocNames = Object.keys(this.model.associations);
|
|
450
689
|
const plainData = {};
|
|
451
690
|
const assocData = {};
|
|
@@ -479,7 +718,7 @@ export class SequelizeModel extends AbstractModel {
|
|
|
479
718
|
}
|
|
480
719
|
// --- DESTROY ONE ---
|
|
481
720
|
async _destroyOne(criteria) {
|
|
482
|
-
const { where } = this.
|
|
721
|
+
const { where } = this._convertAdminizerCriteriaToSequelizeOptions(criteria);
|
|
483
722
|
const record = await this.model.findOne({ where });
|
|
484
723
|
if (!record)
|
|
485
724
|
return null;
|
|
@@ -514,7 +753,7 @@ export class SequelizeModel extends AbstractModel {
|
|
|
514
753
|
}
|
|
515
754
|
// --- DESTROY MANY ---
|
|
516
755
|
async _destroy(criteria) {
|
|
517
|
-
const { where } = this.
|
|
756
|
+
const { where } = this._convertAdminizerCriteriaToSequelizeOptions(criteria);
|
|
518
757
|
const records = await this.model.findAll({ where });
|
|
519
758
|
const assocNames = Object.keys(this.model.associations);
|
|
520
759
|
for (const record of records) {
|
|
@@ -551,14 +790,122 @@ export class SequelizeModel extends AbstractModel {
|
|
|
551
790
|
}
|
|
552
791
|
// --- COUNT ---
|
|
553
792
|
async _count(criteria = {}) {
|
|
554
|
-
const { where } = this.
|
|
555
|
-
const
|
|
793
|
+
const { where } = this._convertAdminizerCriteriaToSequelizeOptions(criteria);
|
|
794
|
+
const assocNames = Object.keys(this.model.associations);
|
|
795
|
+
const include = assocNames.map((association) => ({ association }));
|
|
796
|
+
const result = await this.model.count({
|
|
797
|
+
where,
|
|
798
|
+
include,
|
|
799
|
+
distinct: true,
|
|
800
|
+
col: this.primaryKey || this.model.primaryKeyAttribute || 'id'
|
|
801
|
+
});
|
|
556
802
|
return result;
|
|
557
803
|
}
|
|
558
804
|
// --- HELPER ---
|
|
805
|
+
_hasRelationPathCondition(input) {
|
|
806
|
+
if (!input || typeof input !== 'object') {
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
if (Array.isArray(input)) {
|
|
810
|
+
return input.some((item) => this._hasRelationPathCondition(item));
|
|
811
|
+
}
|
|
812
|
+
const keys = [
|
|
813
|
+
...Object.keys(input),
|
|
814
|
+
...Object.getOwnPropertySymbols(input)
|
|
815
|
+
];
|
|
816
|
+
for (const key of keys) {
|
|
817
|
+
if (typeof key === 'string' && /^\$[A-Za-z0-9_]+\.[A-Za-z0-9_]+\$$/.test(key)) {
|
|
818
|
+
return true;
|
|
819
|
+
}
|
|
820
|
+
const value = input[key];
|
|
821
|
+
if (value && typeof value === 'object' && this._hasRelationPathCondition(value)) {
|
|
822
|
+
return true;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
return false;
|
|
826
|
+
}
|
|
827
|
+
_extractRelationAliasesFromWhere(input, aliases = new Set()) {
|
|
828
|
+
if (!input || typeof input !== 'object') {
|
|
829
|
+
return aliases;
|
|
830
|
+
}
|
|
831
|
+
if (Array.isArray(input)) {
|
|
832
|
+
input.forEach((item) => this._extractRelationAliasesFromWhere(item, aliases));
|
|
833
|
+
return aliases;
|
|
834
|
+
}
|
|
835
|
+
const keys = [
|
|
836
|
+
...Object.keys(input),
|
|
837
|
+
...Object.getOwnPropertySymbols(input)
|
|
838
|
+
];
|
|
839
|
+
for (const key of keys) {
|
|
840
|
+
if (typeof key === 'string') {
|
|
841
|
+
const match = key.match(/^\$([A-Za-z0-9_]+)\.[A-Za-z0-9_]+\$$/);
|
|
842
|
+
if (match?.[1]) {
|
|
843
|
+
aliases.add(match[1]);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
const value = input[key];
|
|
847
|
+
if (value && typeof value === 'object') {
|
|
848
|
+
this._extractRelationAliasesFromWhere(value, aliases);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
return aliases;
|
|
852
|
+
}
|
|
853
|
+
_buildListInclude(alias, usedRelationAliases, hasRelationPathCondition) {
|
|
854
|
+
const include = { association: alias };
|
|
855
|
+
if (!hasRelationPathCondition) {
|
|
856
|
+
return include;
|
|
857
|
+
}
|
|
858
|
+
const association = this.model.associations[alias];
|
|
859
|
+
const isUsedInWhere = usedRelationAliases.has(alias);
|
|
860
|
+
const isHasMany = association instanceof HasMany || association?.associationType === 'HasMany';
|
|
861
|
+
// When filtering by relation path ($alias.field$), extra joined hasMany associations
|
|
862
|
+
// can duplicate base rows and break page size. Load such associations separately.
|
|
863
|
+
if (isHasMany && !isUsedInWhere) {
|
|
864
|
+
include.separate = true;
|
|
865
|
+
}
|
|
866
|
+
return include;
|
|
867
|
+
}
|
|
559
868
|
_buildIncludes() {
|
|
560
869
|
return Object.keys(this.model.associations).map(key => ({ association: key }));
|
|
561
870
|
}
|
|
871
|
+
_buildAttributes(select) {
|
|
872
|
+
if (!select) {
|
|
873
|
+
return undefined;
|
|
874
|
+
}
|
|
875
|
+
if (Array.isArray(select)) {
|
|
876
|
+
const attributes = select.filter((field) => this.model.rawAttributes?.[field]);
|
|
877
|
+
return attributes.length ? attributes : undefined;
|
|
878
|
+
}
|
|
879
|
+
const attributes = Object.entries(select)
|
|
880
|
+
.filter(([field, enabled]) => enabled && this.model.rawAttributes?.[field])
|
|
881
|
+
.map(([field]) => field);
|
|
882
|
+
return attributes.length ? attributes : undefined;
|
|
883
|
+
}
|
|
884
|
+
_buildCriteriaIncludes(populate) {
|
|
885
|
+
return Object.entries(populate).map(([association, nestedCriteria]) => {
|
|
886
|
+
if (nestedCriteria === true) {
|
|
887
|
+
return { association };
|
|
888
|
+
}
|
|
889
|
+
const { where, attributes, order, limit, } = this._convertAdminizerCriteriaToSequelizeOptions(nestedCriteria);
|
|
890
|
+
const include = { association };
|
|
891
|
+
if (where && (Object.keys(where).length > 0 || Object.getOwnPropertySymbols(where).length > 0)) {
|
|
892
|
+
include.where = where;
|
|
893
|
+
}
|
|
894
|
+
if (attributes) {
|
|
895
|
+
include.attributes = attributes;
|
|
896
|
+
}
|
|
897
|
+
if (order) {
|
|
898
|
+
include.order = order;
|
|
899
|
+
}
|
|
900
|
+
if (typeof limit === "number") {
|
|
901
|
+
include.limit = limit;
|
|
902
|
+
}
|
|
903
|
+
if (nestedCriteria.populate) {
|
|
904
|
+
include.include = this._buildCriteriaIncludes(nestedCriteria.populate);
|
|
905
|
+
}
|
|
906
|
+
return include;
|
|
907
|
+
});
|
|
908
|
+
}
|
|
562
909
|
}
|
|
563
910
|
export class SequelizeAdapter extends AbstractAdapter {
|
|
564
911
|
sequelize;
|
|
@@ -629,7 +976,7 @@ export class SequelizeAdapter extends AbstractAdapter {
|
|
|
629
976
|
}
|
|
630
977
|
}
|
|
631
978
|
}
|
|
632
|
-
/**Generation of the
|
|
979
|
+
/** Generation of the Sequelize model from Adminizer's model definition */
|
|
633
980
|
function generateSequelizeModel(sequelize, modelName, rawAttributes) {
|
|
634
981
|
const attributes = {};
|
|
635
982
|
let primaryKey = null;
|