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.
Files changed (335) hide show
  1. package/Readme.md +12 -0
  2. package/assets/RelationDialogStack-CnHFOYyR.css +1 -0
  3. package/assets/RelationDialogStack-DI2bZxQX.js +20 -0
  4. package/assets/add-4213XScp.js +1 -0
  5. package/assets/add-group--F1XxfJ1.js +1 -0
  6. package/assets/add-group-form-DKRXC2n-.js +1 -0
  7. package/assets/add-user-b9HfMq7z.js +1 -0
  8. package/assets/add-user-form-UXAToWQd.js +1 -0
  9. package/assets/app-Clx8f5hd.css +1 -0
  10. package/assets/app-layout-C02ja4OI.js +1 -0
  11. package/assets/app-shell-D8wtkECO.js +1 -0
  12. package/assets/app-sidebar-BR5_zwxj.js +1 -0
  13. package/assets/app-sidebar-header-BZbIBQjq.js +1 -0
  14. package/assets/app.js +59 -7990
  15. package/assets/breadcrumbs-CODaqVkB.js +1 -0
  16. package/assets/catalog-6ZZjWdFs.js +6 -0
  17. package/assets/dashboard-CftAdKiH.js +3 -0
  18. package/assets/date-picker-DSOkNiNo.js +1 -0
  19. package/assets/del-modal-Cd4M7af8.js +1 -0
  20. package/assets/dynamic-controls-BsrJhPDY.js +1 -0
  21. package/assets/geo-json-CzR5JBXL.js +1 -0
  22. package/assets/handsontable-BI98OF5R.js +336 -0
  23. package/assets/history-DLHSU7Ak.js +1 -0
  24. package/assets/home-NcQ7nnU0.js +2 -0
  25. package/assets/icon-C2JNaLxH.js +1 -0
  26. package/assets/init-user-D2HSyXup.js +1 -0
  27. package/assets/input-error-CH2caTtO.js +1 -0
  28. package/assets/jsoneditor-DVNQmA6G.js +3869 -0
  29. package/assets/list-CFU0-TgN.js +4 -0
  30. package/assets/login-DFSLEO17.js +1 -0
  31. package/assets/manifest.json +246 -273
  32. package/assets/material-icon-Bx9pGQiQ.js +1 -0
  33. package/assets/media-manager-WcKe1EvD.js +6 -0
  34. package/assets/module-CT7PC6pc.js +1 -0
  35. package/assets/monaco-Bl4zdWCn.js +11 -0
  36. package/assets/nav-footer-BiSQkPt4.js +1 -0
  37. package/assets/nav-main-rTH0PfDw.js +1 -0
  38. package/assets/nav-user-Bj6cWs0a.js +1 -0
  39. package/assets/notification-Bi-PX87D.js +20 -0
  40. package/assets/pagination-render-AKJvTIRA.js +1 -0
  41. package/assets/register-BTP4uxsK.js +1 -0
  42. package/assets/ru-eW0WKkga.js +1 -0
  43. package/assets/theme-switcher-Dm8EOGzx.js +1 -0
  44. package/assets/toast-editor-DOg_gvSp.js +668 -0
  45. package/assets/user-filters-list-YUfR4XMI.js +1 -0
  46. package/assets/user-info-BgSbT02v.js +1 -0
  47. package/assets/user-menu-content-TQTXHIv6.js +1 -0
  48. package/assets/{vanilla-picker-l5rcX3cq.js → vanilla-picker-B3LgXoKV.js} +1 -7
  49. package/assets/welcome-vLwTu1R6.js +1 -0
  50. package/controllers/add.d.ts +1 -1
  51. package/controllers/add.js +20 -26
  52. package/controllers/addGroup.d.ts +1 -1
  53. package/controllers/addGroup.js +7 -15
  54. package/controllers/addUser.d.ts +1 -1
  55. package/controllers/addUser.js +10 -16
  56. package/controllers/catalog/Catalog.d.ts +1 -1
  57. package/controllers/catalog/Catalog.js +1 -10
  58. package/controllers/catalog/FrontendCatalogAdapter.d.ts +3 -3
  59. package/controllers/catalog/FrontendCatalogAdapter.js +0 -6
  60. package/controllers/catalog/FrontentCatalogAdapter.d.ts +3 -3
  61. package/controllers/catalog/FrontentCatalogAdapter.js +0 -4
  62. package/controllers/ckeditorUpload.js +2 -18
  63. package/controllers/dashboard.d.ts +1 -1
  64. package/controllers/dashboard.js +2 -5
  65. package/controllers/edit.js +24 -37
  66. package/controllers/editGroup.d.ts +1 -1
  67. package/controllers/editGroup.js +10 -21
  68. package/controllers/editUser.d.ts +1 -1
  69. package/controllers/editUser.js +11 -23
  70. package/controllers/exportData.d.ts +6 -0
  71. package/controllers/exportData.js +315 -0
  72. package/controllers/feed.d.ts +6 -0
  73. package/controllers/feed.js +73 -0
  74. package/controllers/feedback/feedbackController.d.ts +1 -1
  75. package/controllers/feedback/feedbackController.js +5 -7
  76. package/controllers/filter-fields/columns.d.ts +11 -0
  77. package/controllers/filter-fields/columns.js +106 -0
  78. package/controllers/filter-fields/filterFields.d.ts +10 -0
  79. package/controllers/filter-fields/filterFields.js +141 -0
  80. package/controllers/filter-fields/getAllUserFilters.d.ts +6 -0
  81. package/controllers/filter-fields/getAllUserFilters.js +115 -0
  82. package/controllers/filter-fields/groups.d.ts +5 -0
  83. package/controllers/filter-fields/groups.js +26 -0
  84. package/controllers/filter-fields/savedFilters.d.ts +25 -0
  85. package/controllers/filter-fields/savedFilters.js +317 -0
  86. package/controllers/getAllWidgets.d.ts +1 -1
  87. package/controllers/getAllWidgets.js +1 -9
  88. package/controllers/history-actions/HistoryController.js +6 -31
  89. package/controllers/initUser.js +6 -5
  90. package/controllers/inlineUpdate.d.ts +7 -0
  91. package/controllers/inlineUpdate.js +148 -0
  92. package/controllers/list.d.ts +1 -1
  93. package/controllers/list.js +383 -70
  94. package/controllers/listUserFilters.d.ts +5 -0
  95. package/controllers/listUserFilters.js +26 -0
  96. package/controllers/login.js +2 -2
  97. package/controllers/media-manager/mediaManagerApi.js +0 -9
  98. package/controllers/navigation/edit.js +3 -9
  99. package/controllers/notifications/NotificationController.d.ts +1 -1
  100. package/controllers/notifications/NotificationController.js +12 -36
  101. package/controllers/register.js +10 -9
  102. package/controllers/remove.js +10 -19
  103. package/controllers/timezones.d.ts +1 -1
  104. package/controllers/timezones.js +0 -6
  105. package/controllers/userApiKey.d.ts +7 -0
  106. package/controllers/userApiKey.js +36 -0
  107. package/controllers/view.js +14 -26
  108. package/controllers/welcome.d.ts +1 -1
  109. package/controllers/welcome.js +0 -4
  110. package/controllers/widgets/Action.d.ts +1 -1
  111. package/controllers/widgets/Action.js +1 -9
  112. package/controllers/widgets/Custom.d.ts +1 -1
  113. package/controllers/widgets/Custom.js +1 -9
  114. package/controllers/widgets/Info.d.ts +1 -1
  115. package/controllers/widgets/Info.js +1 -9
  116. package/controllers/widgets/filterInfo.d.ts +1 -0
  117. package/controllers/widgets/filterInfo.js +44 -0
  118. package/controllers/widgets/switch.d.ts +1 -1
  119. package/controllers/widgets/switch.js +1 -9
  120. package/controllers/widgetsDB.d.ts +1 -1
  121. package/controllers/widgetsDB.js +2 -8
  122. package/helpers/accessRightsHelper.d.ts +1 -1
  123. package/helpers/accessRightsHelper.js +5 -4
  124. package/helpers/apiKeyHelper.d.ts +5 -0
  125. package/helpers/apiKeyHelper.js +8 -0
  126. package/helpers/configHelper.js +12 -15
  127. package/helpers/controllerHelper.d.ts +14 -21
  128. package/helpers/controllerHelper.js +50 -83
  129. package/helpers/fieldsHelper.js +3 -3
  130. package/helpers/filterDatetimeHelper.d.ts +5 -0
  131. package/helpers/filterDatetimeHelper.js +84 -0
  132. package/helpers/inertiaActionsHelper.d.ts +2 -2
  133. package/helpers/inertiaActionsHelper.js +5 -4
  134. package/helpers/inertiaAddHelper.d.ts +3 -414
  135. package/helpers/inertiaAddHelper.js +39 -15
  136. package/helpers/inertiaGroupHelper.d.ts +2 -2
  137. package/helpers/inertiaGroupHelper.js +3 -3
  138. package/helpers/inertiaListHelper.d.ts +7 -3
  139. package/helpers/inertiaListHelper.js +26 -12
  140. package/helpers/inertiaMenuHelper.d.ts +1 -0
  141. package/helpers/inertiaMenuHelper.js +15 -3
  142. package/helpers/inertiaUserHelper.d.ts +3 -2
  143. package/helpers/inertiaUserHelper.js +7 -3
  144. package/helpers/menuHelper.d.ts +5 -2
  145. package/helpers/menuHelper.js +64 -12
  146. package/helpers/viteHelper.js +1 -2
  147. package/index.d.ts +6 -2
  148. package/index.js +6 -2
  149. package/interfaces/adminpanelConfig.d.ts +126 -39
  150. package/interfaces/internalModelAccess.d.ts +37 -0
  151. package/interfaces/internalModelAccess.js +1 -0
  152. package/interfaces/listQueryBuilder.d.ts +22 -0
  153. package/interfaces/listQueryBuilder.js +1 -0
  154. package/interfaces/queryCriteria.d.ts +54 -0
  155. package/interfaces/queryCriteria.js +1 -0
  156. package/interfaces/types.d.ts +1 -13
  157. package/lib/Adminizer.d.ts +4 -2
  158. package/lib/Adminizer.js +29 -10
  159. package/lib/DataAccessor.d.ts +8 -5
  160. package/lib/DataAccessor.js +89 -42
  161. package/lib/I18n.d.ts +5 -1
  162. package/lib/I18n.js +45 -19
  163. package/lib/MiddlewareManager.d.ts +8 -0
  164. package/lib/MiddlewareManager.js +53 -0
  165. package/lib/ai-assistant/OpenAiModelService.d.ts +3 -3
  166. package/lib/ai-assistant/OpenAiModelService.js +23 -24
  167. package/lib/catalog/Navigation.d.ts +1 -0
  168. package/lib/catalog/Navigation.js +12 -11
  169. package/lib/controls/jsoneditor/JsonEditor.d.ts +1 -2
  170. package/lib/controls/markdown/ToastUiEditor.d.ts +1 -2
  171. package/lib/feedback/FeedbackHandler.js +2 -2
  172. package/lib/filters/ConditionValidator.d.ts +107 -0
  173. package/lib/filters/ConditionValidator.js +483 -0
  174. package/lib/filters/CustomFilterHandler.d.ts +122 -0
  175. package/lib/filters/CustomFilterHandler.js +131 -0
  176. package/lib/filters/FilterCustomFieldHandler.d.ts +2 -0
  177. package/lib/filters/FilterCustomFieldHandler.js +2 -0
  178. package/lib/filters/FilterService.d.ts +82 -0
  179. package/lib/filters/FilterService.js +323 -0
  180. package/lib/filters/index.d.ts +6 -0
  181. package/lib/filters/index.js +4 -0
  182. package/lib/history-actions/AbstractHistoryAdapter.d.ts +5 -4
  183. package/lib/history-actions/AbstractHistoryAdapter.js +45 -30
  184. package/lib/history-actions/DefaultHistoryAdapter.d.ts +2 -0
  185. package/lib/history-actions/DefaultHistoryAdapter.js +12 -6
  186. package/lib/history-actions/HistoryHandler.js +1 -1
  187. package/lib/list-query-builder/ListQueryBuilder.d.ts +117 -0
  188. package/lib/list-query-builder/ListQueryBuilder.js +707 -0
  189. package/lib/media-manager/AbstractMediaManager.d.ts +9 -8
  190. package/lib/media-manager/DefaultMediaManager.d.ts +2 -0
  191. package/lib/media-manager/DefaultMediaManager.js +27 -15
  192. package/lib/media-manager/Items.d.ts +4 -1
  193. package/lib/media-manager/Items.js +62 -59
  194. package/lib/media-manager/MediaManagerHandler.js +1 -1
  195. package/lib/media-manager/helpers/MediaManagerHelper.d.ts +5 -1
  196. package/lib/media-manager/helpers/MediaManagerHelper.js +38 -4
  197. package/lib/model/AbstractModel.d.ts +33 -25
  198. package/lib/model/AbstractModel.js +54 -2
  199. package/lib/model/InternalModelAccessFactory.d.ts +18 -0
  200. package/lib/model/InternalModelAccessFactory.js +82 -0
  201. package/lib/model/ModelHandler.d.ts +6 -0
  202. package/lib/model/ModelHandler.js +14 -0
  203. package/lib/model/adapter/sequelize.d.ts +32 -10
  204. package/lib/model/adapter/sequelize.js +409 -62
  205. package/lib/model/adapter/typeorm.d.ts +62 -0
  206. package/lib/model/adapter/typeorm.js +777 -0
  207. package/lib/model/internalModelAccessToken.d.ts +1 -0
  208. package/lib/model/internalModelAccessToken.js +1 -0
  209. package/lib/model/internalModelRepository.d.ts +3 -0
  210. package/lib/model/internalModelRepository.js +4 -0
  211. package/lib/notifications/AbstractNotificationService.d.ts +3 -0
  212. package/lib/notifications/AbstractNotificationService.js +42 -36
  213. package/lib/notifications/GeneralNotificationService.js +37 -40
  214. package/lib/notifications/SystemNotificationService.js +33 -36
  215. package/lib/requestProcessor.js +0 -3
  216. package/lib/ui-i18n/getUiTranslations.d.ts +1 -0
  217. package/lib/ui-i18n/getUiTranslations.js +3 -0
  218. package/lib/ui-i18n/uiTranslationKeys.d.ts +6 -0
  219. package/lib/ui-i18n/uiTranslationKeys.js +268 -0
  220. package/lib/widgets/widgetHandler.d.ts +7 -2
  221. package/lib/widgets/widgetHandler.js +98 -18
  222. package/models/FilterAP.d.ts +116 -0
  223. package/models/FilterAP.js +68 -0
  224. package/models/FilterColumnAP.d.ts +32 -0
  225. package/models/FilterColumnAP.js +21 -0
  226. package/models/GroupAP.d.ts +1 -1
  227. package/models/HistoryActionsAP.d.ts +3 -3
  228. package/models/MediaManagerMetaAP.d.ts +1 -1
  229. package/models/NavigationAP.d.ts +1 -1
  230. package/models/UserAP.d.ts +6 -1
  231. package/models/UserAP.js +4 -0
  232. package/package.json +39 -32
  233. package/policies/authPolicies.d.ts +12 -0
  234. package/policies/authPolicies.js +104 -0
  235. package/policies/permissionResolvers.d.ts +12 -0
  236. package/policies/permissionResolvers.js +52 -0
  237. package/services/FeedService.d.ts +70 -0
  238. package/services/FeedService.js +334 -0
  239. package/system/Router.d.ts +1 -0
  240. package/system/Router.js +136 -59
  241. package/system/bindAccessRights.js +11 -30
  242. package/system/bindAuthorization.js +18 -18
  243. package/system/bindCors.js +16 -1
  244. package/system/bindCustomFilterHandlers.d.ts +2 -0
  245. package/system/bindCustomFilterHandlers.js +4 -0
  246. package/system/bindInertia.js +18 -61
  247. package/system/bindReqFunctions.js +10 -2
  248. package/system/bindTranslations.js +7 -3
  249. package/system/buildInternalModelAccess.d.ts +4 -0
  250. package/system/buildInternalModelAccess.js +59 -0
  251. package/system/defaults.js +18 -14
  252. package/system/routePolicies.d.ts +2 -0
  253. package/system/routePolicies.js +29 -0
  254. package/translations/en.json +151 -39
  255. package/translations/ru.json +211 -64
  256. package/ui/components/handsontable.js +13 -27
  257. package/ui/components/ui/dialog-stack.js +1 -3
  258. package/ui/components/ui/popover.js +1 -1
  259. package/ui/components/ui/sidebar.js +1 -1
  260. package/assets/RelationDialogStack-D9UVEF9s.js +0 -407
  261. package/assets/RelationDialogStack-DV8OITPR.css +0 -4
  262. package/assets/add-BlvzWQKF.js +0 -1
  263. package/assets/add-group-1Uoq9NWe.js +0 -1
  264. package/assets/add-group-form-B01gwDGu.js +0 -1
  265. package/assets/add-user-BQCAsaRy.js +0 -1
  266. package/assets/add-user-form-Cgwc9JkI.js +0 -1
  267. package/assets/app-DaOMy1nl.css +0 -72
  268. package/assets/app-layout-CAm60qoE.js +0 -1
  269. package/assets/app-shell-QWCtn9ye.js +0 -1
  270. package/assets/app-sidebar-CfaYbnSy.js +0 -1
  271. package/assets/app-sidebar-header-BsPFDhZ8.js +0 -1
  272. package/assets/breadcrumbs-CZZirDj8.js +0 -1
  273. package/assets/catalog-k5myk7fk.js +0 -6
  274. package/assets/dashboard-BkL9ybBw.js +0 -3
  275. package/assets/date-picker-B5AZ_7Q9.js +0 -1
  276. package/assets/del-modal-BoyXZloG.js +0 -1
  277. package/assets/dynamic-controls-k61T9W2P.js +0 -1
  278. package/assets/form-1WGY0YgG.js +0 -1
  279. package/assets/form-DJapG3qF.js +0 -1
  280. package/assets/geo-json-DsMUHo0l.js +0 -4
  281. package/assets/handsontable-PxYdjN8c.js +0 -298
  282. package/assets/history-CluyR-yJ.js +0 -1
  283. package/assets/home-Dug9zBzM.js +0 -2
  284. package/assets/icon-DfDLMG5f.js +0 -1
  285. package/assets/init-user-CF80MEnT.js +0 -1
  286. package/assets/input-error-DuuqFgZa.js +0 -1
  287. package/assets/jsoneditor-De1FvcYu.js +0 -3882
  288. package/assets/list-CuexshNt.js +0 -1
  289. package/assets/list-table-BsEWjTKY.js +0 -22
  290. package/assets/login-eDemYzN-.js +0 -1
  291. package/assets/material-icon-DMT8KdmE.js +0 -1
  292. package/assets/media-manager-DfYa_mT_.js +0 -14
  293. package/assets/module-Cd4Tm6uq.js +0 -1
  294. package/assets/monaco-BhBYtg3d.js +0 -11
  295. package/assets/nav-footer-D3olYxtz.js +0 -1
  296. package/assets/nav-main-BctqyzeV.js +0 -1
  297. package/assets/nav-user-Bn1Lt-BZ.js +0 -1
  298. package/assets/notification-D64qvni-.js +0 -27
  299. package/assets/pagination-render-DxD78ahJ.js +0 -1
  300. package/assets/register-349PwYbe.js +0 -1
  301. package/assets/theme-switcher-DfExsHW2.js +0 -1
  302. package/assets/toast-editor-ESK5iWPx.js +0 -845
  303. package/assets/user-info-DtBK5Kh8.js +0 -1
  304. package/assets/user-menu-content-0XYOCMaA.js +0 -1
  305. package/assets/welcome-PDp-gUei.js +0 -1
  306. package/controllers/form.d.ts +0 -1
  307. package/controllers/form.js +0 -86
  308. package/helpers/formHelper.d.ts +0 -6
  309. package/helpers/formHelper.js +0 -38
  310. package/helpers/inertiaFromHelper.d.ts +0 -11
  311. package/helpers/inertiaFromHelper.js +0 -98
  312. package/helpers/runtimeMode.d.ts +0 -1
  313. package/helpers/runtimeMode.js +0 -3
  314. package/lib/PolicyManager.d.ts +0 -8
  315. package/lib/PolicyManager.js +0 -53
  316. package/lib/datatable/NodeTable.d.ts +0 -49
  317. package/lib/datatable/NodeTable.js +0 -207
  318. package/lib/model/adapter/waterline.d.ts +0 -32
  319. package/lib/model/adapter/waterline.js +0 -180
  320. package/migrations/index.d.ts +0 -3
  321. package/migrations/index.js +0 -4
  322. package/migrations/knex/20250106000001_adminizer_init.d.ts +0 -8
  323. package/migrations/knex/20250106000001_adminizer_init.js +0 -164
  324. package/migrations/umzug/0001.d.ts +0 -7
  325. package/migrations/umzug/0001.js +0 -121
  326. package/migrations/umzugExports.d.ts +0 -2
  327. package/migrations/umzugExports.js +0 -9
  328. package/system/bindForms.d.ts +0 -2
  329. package/system/bindForms.js +0 -20
  330. /package/{policies → middlewares}/addMessages.d.ts +0 -0
  331. /package/{policies → middlewares}/addMessages.js +0 -0
  332. /package/{policies → middlewares}/checkAuth.d.ts +0 -0
  333. /package/{policies → middlewares}/checkAuth.js +0 -0
  334. /package/{policies → middlewares}/setLocale.d.ts +0 -0
  335. /package/{policies → middlewares}/setLocale.js +0 -0
@@ -1,122 +1,435 @@
1
1
  import { ControllerHelper } from "../helpers/controllerHelper.js";
2
2
  import { DataAccessor } from "../lib/DataAccessor.js";
3
- import { NodeTable } from "../lib/datatable/NodeTable.js";
3
+ import { ListQueryBuilder } from "../lib/list-query-builder/ListQueryBuilder.js";
4
4
  import { Adminizer } from "../lib/Adminizer.js";
5
5
  import { inertiaListHelper } from "../helpers/inertiaListHelper.js";
6
- import { redirectToLogin } from '../helpers/inertiaAutHelper.js';
6
+ import { FilterService } from "../lib/filters/FilterService.js";
7
+ import { convertDatetimeConditions } from "../helpers/filterDatetimeHelper.js";
8
+ import { getUiTranslations } from "../lib/ui-i18n/getUiTranslations.js";
9
+ import { FILTER_UI_TRANSLATION_KEYS } from "../lib/ui-i18n/uiTranslationKeys.js";
7
10
  export default async function list(req, res) {
8
- let entity = ControllerHelper.findEntityObject(req);
9
- if (!entity.model) {
11
+ let modelResource = ControllerHelper.findModelResource(req);
12
+ if (!modelResource.model) {
10
13
  return res.status(404).send({ error: 'Not Found' });
11
14
  }
12
- if (req.adminizer.config.auth.enable) {
13
- if (!req.user) {
14
- return redirectToLogin(req, res);
15
- }
16
- else if (!req.adminizer.accessRightsHelper.hasPermission(`read-${entity.name}-model`, req.user)) {
17
- return res.sendStatus(403);
18
- }
19
- }
20
- let dataAccessor = new DataAccessor(req.adminizer, req.user, entity, "list");
15
+ let dataAccessor = new DataAccessor(req.adminizer, req.user, modelResource, "list");
21
16
  let fields = dataAccessor.getFieldsConfig();
22
- const header = inertiaListHelper(entity, req, fields);
23
- let start = "0";
24
- if (req.query.page) {
25
- if (req.query.page === '1') {
26
- start = "0";
17
+ const header = inertiaListHelper(modelResource, req, fields);
18
+ const i18nPage = getUiTranslations(req, FILTER_UI_TRANSLATION_KEYS);
19
+ // Parse pagination
20
+ const defaultPageSize = req.adminizer.config.list?.defaultPageSize ?? 50;
21
+ const page = req.query.page ? parseInt(req.query.page.toString(), 10) : 1;
22
+ const limit = req.query.count ? parseInt(req.query.count.toString(), 10) : defaultPageSize;
23
+ // Parse sorting
24
+ const orderColumn = req.query.column ? req.query.column.toString() : undefined;
25
+ const direction = req.query.direction === "asc" ? 'ASC' : 'DESC';
26
+ // Parse global search
27
+ const globalSearch = req.query.globalSearch ? req.query.globalSearch.toString() : "";
28
+ // Check for saved filter (filterId parameter)
29
+ const filterId = req.query.filterId ? req.query.filterId.toString() : undefined;
30
+ let savedFilter = null;
31
+ let savedColumns = [];
32
+ let filterError = null;
33
+ if (filterId) {
34
+ try {
35
+ // Handle temporary filter from session (keyed by model name)
36
+ if (filterId === 'temporary' && req.session?.temporaryFilters?.[modelResource.name]) {
37
+ const tempFilter = req.session.temporaryFilters[modelResource.name];
38
+ savedFilter = {
39
+ id: 'temporary',
40
+ name: tempFilter.name,
41
+ conditions: tempFilter.conditions,
42
+ modelName: modelResource.name,
43
+ visibility: 'private',
44
+ ownerId: req.user?.id || null,
45
+ apiEnabled: false,
46
+ version: 1,
47
+ createdAt: new Date(),
48
+ updatedAt: new Date()
49
+ };
50
+ // Use temporary filter columns if available
51
+ if (tempFilter.columns && Array.isArray(tempFilter.columns)) {
52
+ savedColumns = tempFilter.columns.map((col, index) => ({
53
+ id: index + 1,
54
+ filter: 'temporary',
55
+ fieldName: col.fieldName,
56
+ order: col.order !== undefined ? col.order : index
57
+ }));
58
+ }
59
+ else {
60
+ savedColumns = [];
61
+ }
62
+ }
63
+ else if (filterId !== 'temporary') {
64
+ // Load saved filter from database
65
+ const filterService = new FilterService(req.adminizer);
66
+ const result = await filterService.getFilterWithColumns(filterId, req.user);
67
+ savedFilter = result.filter;
68
+ savedColumns = result.columns;
69
+ if (!savedFilter) {
70
+ filterError = 'Filter not found or access denied';
71
+ }
72
+ }
73
+ else {
74
+ filterError = 'Temporary filter not found in session';
75
+ }
27
76
  }
28
- else {
29
- start = ((+req.query.page - 1) * +req.query.count).toString();
77
+ catch (err) {
78
+ Adminizer.log.error('Error loading filter:', err);
79
+ filterError = 'Error loading filter';
30
80
  }
31
81
  }
32
- const count = req.query.count ? req.query.count.toString() : "5";
33
- const orderColumn = req.query.column ? req.query.column.toString() : undefined; // if no column is set, default to first column
34
- const direction = req.query.direction === "asc" ? 'asc' : "desc";
35
- const globalSearch = req.query.globalSearch ? req.query.globalSearch.toString() : "";
82
+ // Update header with active filter name
83
+ if (savedFilter && savedFilter.name) {
84
+ header.activeFilterName = savedFilter.name;
85
+ }
86
+ // Parse column-specific search
36
87
  const searchColumns = req.query.searchColumn
37
88
  ? Array.isArray(req.query.searchColumn)
38
- ? req.query.searchColumn.map(String)
39
- : [req.query.searchColumn.toString()]
89
+ ? req.query.searchColumn.map(String).filter(c => c && c.trim() !== '')
90
+ : [String(req.query.searchColumn)].filter(c => c && c.trim() !== '')
40
91
  : [];
41
92
  const searchColumnValues = req.query.searchColumnValue
42
93
  ? Array.isArray(req.query.searchColumnValue)
43
- ? req.query.searchColumnValue.map(String)
44
- : [req.query.searchColumnValue.toString()]
94
+ ? req.query.searchColumnValue.map(v => v ? String(v) : "")
95
+ : [String(req.query.searchColumnValue)]
45
96
  : [];
46
- // Collect {column, value} pairs, removing duplicate columns (leaving the latter)
97
+ // Collect {column, value} pairs, removing duplicate columns
47
98
  const searchMap = new Map();
48
99
  for (let i = 0; i < searchColumns.length; i++) {
49
100
  const column = searchColumns[i];
50
101
  const value = searchColumnValues[i] || "";
51
- searchMap.set(column, value);
102
+ // Only add non-empty values
103
+ if (value && value.trim() !== '') {
104
+ searchMap.set(column, value);
105
+ }
52
106
  }
53
107
  const searchPairs = Array.from(searchMap.entries()).map(([column, value]) => ({
54
108
  column,
55
109
  value,
56
110
  }));
57
- const { columns, nodeTreeColumns } = setColumns(fields, orderColumn, direction, searchPairs, req);
58
- const RequestBody = {
59
- draw: "1",
60
- start: start,
61
- length: count,
62
- order: [
63
- {
64
- column: orderColumn,
65
- dir: direction
66
- }
67
- ],
68
- columns: [
69
- {
70
- data: "0", // actions column
71
- searchable: "true",
72
- orderable: "true",
73
- search: { value: "", regex: false }
74
- },
75
- ...nodeTreeColumns
76
- ],
77
- search: { value: globalSearch, regex: false }
111
+ // Build filter conditions
112
+ let filters = [];
113
+ // If saved filter, use its conditions (with datetime conversion)
114
+ if (savedFilter && savedFilter.conditions) {
115
+ filters = convertDatetimeConditions(savedFilter.conditions, { dropEmptyValues: true });
116
+ }
117
+ // Apply custom columns if filter has them
118
+ let displayFields = getVisibleFields(fields);
119
+ let customColumnsConfig = null;
120
+ if (savedColumns.length > 0) {
121
+ customColumnsConfig = savedColumns;
122
+ // Filter and reorder fields based on custom column configuration
123
+ displayFields = applyCustomColumns(displayFields, savedColumns);
124
+ }
125
+ // Add column search conditions (with datetime conversion)
126
+ const searchFilters = buildFiltersFromSearchPairs(displayFields, searchPairs);
127
+ if (searchFilters && searchFilters.length > 0) {
128
+ filters = [...filters, ...convertDatetimeConditions(searchFilters, { dropEmptyValues: true })];
129
+ }
130
+ // Prepare columns for frontend
131
+ const columns = setColumns(displayFields, orderColumn, direction.toLowerCase(), searchPairs, req);
132
+ // Convert column index to field name based on display fields
133
+ const sortField = orderColumn ? getFieldNameByIndex(displayFields, parseInt(orderColumn, 10)) : undefined;
134
+ // Determine sort field - prefer URL param, then filter setting
135
+ let effectiveSortField = sortField;
136
+ let effectiveSortDirection = direction;
137
+ if (!effectiveSortField && savedFilter?.sortField) {
138
+ effectiveSortField = savedFilter.sortField;
139
+ effectiveSortDirection = savedFilter.sortDirection || 'DESC';
140
+ }
141
+ // Build query parameters
142
+ const queryParams = {
143
+ page,
144
+ limit,
145
+ sort: effectiveSortField,
146
+ sortDirection: effectiveSortDirection,
147
+ filters: filters && filters.length > 0 ? filters : undefined,
148
+ globalSearch: globalSearch || undefined
78
149
  };
79
- const nodeTable = new NodeTable(RequestBody, entity.model, fields);
80
- await nodeTable.output((err, data) => {
81
- if (err) {
82
- Adminizer.log.error(err);
150
+ // Execute query using ListQueryBuilder (use original fields for query, displayFields for output)
151
+ const listQueryBuilder = new ListQueryBuilder(modelResource.model, fields, dataAccessor, req.adminizer.customFilterHandler);
152
+ try {
153
+ const result = await listQueryBuilder.execute(queryParams);
154
+ // Build active filter info for frontend
155
+ const activeFilter = savedFilter ? {
156
+ id: savedFilter.id,
157
+ name: savedFilter.name,
158
+ description: savedFilter.description,
159
+ icon: savedFilter.icon,
160
+ color: savedFilter.color,
161
+ conditions: savedFilter.conditions
162
+ } : null;
163
+ // Build field name mapping for display
164
+ const fieldNames = {};
165
+ Object.entries(fields).forEach(([key, field]) => {
166
+ if (field && typeof field === 'object') {
167
+ fieldNames[key] = field.title || key;
168
+ }
169
+ });
170
+ // Format conditions with field names for display
171
+ const formattedConditions = savedFilter?.conditions?.map((cond) => {
172
+ if (cond.field) {
173
+ return {
174
+ ...cond,
175
+ fieldName: fieldNames[cond.field] || cond.field
176
+ };
177
+ }
178
+ if (cond.customHandler) {
179
+ const customHandlerName = cond.customHandlerName
180
+ || req.adminizer.customFilterHandler?.get(cond.customHandler)?.name;
181
+ const fallbackField = typeof cond.customHandler === 'string'
182
+ ? cond.customHandler.split('.').pop()
183
+ : '';
184
+ const resolvedField = fallbackField && fieldNames[fallbackField]
185
+ ? fieldNames[fallbackField]
186
+ : (fallbackField || cond.customHandler);
187
+ return {
188
+ ...cond,
189
+ fieldName: resolvedField,
190
+ customHandlerName
191
+ };
192
+ }
193
+ if (cond.relation && cond.relationField) {
194
+ const relationField = fields[cond.relation];
195
+ const relationLabel = relationField?.config?.title || cond.relation;
196
+ const nestedLabel = relationField?.populated?.[cond.relationField]?.config?.title || cond.relationField;
197
+ return {
198
+ ...cond,
199
+ fieldName: `${relationLabel}.${nestedLabel}`
200
+ };
201
+ }
202
+ return {
203
+ ...cond,
204
+ fieldName: ''
205
+ };
206
+ }) || [];
207
+ // Update header with active filter name
208
+ if (savedFilter && savedFilter.name) {
209
+ header.activeFilterName = savedFilter.name;
210
+ header.activeFilterConditions = formattedConditions;
83
211
  }
84
212
  return req.Inertia.render({
85
213
  component: 'list',
86
214
  props: {
87
215
  header: header,
88
216
  columns: columns,
89
- data: data ?? [],
217
+ data: {
218
+ data: result.data,
219
+ recordsTotal: result.total,
220
+ recordsFiltered: result.filtered,
221
+ page: result.page,
222
+ pages: result.pages
223
+ },
224
+ activeFilter: activeFilter,
225
+ filterError: filterError,
226
+ customColumns: customColumnsConfig,
227
+ i18nPage
228
+ }
229
+ });
230
+ }
231
+ catch (err) {
232
+ Adminizer.log.error(err);
233
+ return req.Inertia.render({
234
+ component: 'list',
235
+ props: {
236
+ header: header,
237
+ columns: columns,
238
+ data: {
239
+ data: [],
240
+ recordsTotal: 0,
241
+ recordsFiltered: 0,
242
+ page: 1,
243
+ pages: 0
244
+ },
245
+ activeFilter: null,
246
+ filterError: filterError || 'Query execution error',
247
+ customColumns: null,
248
+ i18nPage
90
249
  }
91
250
  });
92
- }, dataAccessor);
251
+ }
93
252
  }
253
+ /**
254
+ * Apply custom column configuration to fields
255
+ * Returns fields filtered and ordered according to saved column config
256
+ */
257
+ function applyCustomColumns(fields, columns) {
258
+ // Sort columns by order
259
+ const sortedColumns = [...columns].sort((a, b) => (a.order || 0) - (b.order || 0));
260
+ // Only include visible columns that exist in fields
261
+ const visibleColumns = sortedColumns.filter(col => fields[col.fieldName]);
262
+ // If no visible columns configured, return original fields
263
+ if (visibleColumns.length === 0) {
264
+ return fields;
265
+ }
266
+ // Build new fields object with custom order
267
+ const result = {};
268
+ for (const col of visibleColumns) {
269
+ const field = fields[col.fieldName];
270
+ if (field) {
271
+ result[col.fieldName] = field;
272
+ }
273
+ }
274
+ return result;
275
+ }
276
+ function getVisibleFields(fields) {
277
+ return Object.entries(fields).reduce((result, [key, field]) => {
278
+ const config = field?.config;
279
+ if (config?.visible === false) {
280
+ return result;
281
+ }
282
+ result[key] = field;
283
+ return result;
284
+ }, {});
285
+ }
286
+ /**
287
+ * Get field name by column index
288
+ */
289
+ function getFieldNameByIndex(fields, index) {
290
+ const fieldNames = Object.keys(fields);
291
+ // Index 0 is usually actions column, so actual fields start from index 1
292
+ const fieldIndex = index - 1;
293
+ return fieldIndex >= 0 && fieldIndex < fieldNames.length ? fieldNames[fieldIndex] : undefined;
294
+ }
295
+ /**
296
+ * Build FilterCondition[] from search pairs
297
+ * Supports:
298
+ * - String: contains search
299
+ * - Number: exact match, > (greater than), < (less than)
300
+ * - Boolean: true/false, 1/0, yes/no
301
+ * - Date: exact match
302
+ */
303
+ function buildFiltersFromSearchPairs(fields, searchPairs) {
304
+ const filters = [];
305
+ const fieldNames = Object.keys(fields);
306
+ for (const pair of searchPairs) {
307
+ // Skip empty values or LIKE patterns (starts and ends with %)
308
+ if (!pair.value || pair.value.trim() === '' ||
309
+ (pair.value.startsWith('%') && pair.value.endsWith('%'))) {
310
+ continue;
311
+ }
312
+ const fieldIndex = parseInt(pair.column, 10) - 1; // Column index starts from 1
313
+ if (fieldIndex < 0 || fieldIndex >= fieldNames.length) {
314
+ continue;
315
+ }
316
+ const fieldName = fieldNames[fieldIndex];
317
+ const field = fields[fieldName];
318
+ const fieldType = field.model?.type;
319
+ // Determine operator based on field type
320
+ let operator = 'like';
321
+ let value = pair.value;
322
+ if (fieldType === 'number' || fieldType === 'integer' || fieldType === 'float') {
323
+ // Support >, <, >=, <= operators for numbers
324
+ const searchStr = pair.value.trim();
325
+ if (searchStr.startsWith('>=')) {
326
+ const numValue = parseFloat(searchStr.substring(2));
327
+ if (!isNaN(numValue)) {
328
+ operator = 'gte';
329
+ value = numValue;
330
+ }
331
+ else {
332
+ continue;
333
+ }
334
+ }
335
+ else if (searchStr.startsWith('>')) {
336
+ const numValue = parseFloat(searchStr.substring(1));
337
+ if (!isNaN(numValue)) {
338
+ operator = 'gt';
339
+ value = numValue;
340
+ }
341
+ else {
342
+ continue;
343
+ }
344
+ }
345
+ else if (searchStr.startsWith('<=')) {
346
+ const numValue = parseFloat(searchStr.substring(2));
347
+ if (!isNaN(numValue)) {
348
+ operator = 'lte';
349
+ value = numValue;
350
+ }
351
+ else {
352
+ continue;
353
+ }
354
+ }
355
+ else if (searchStr.startsWith('<')) {
356
+ const numValue = parseFloat(searchStr.substring(1));
357
+ if (!isNaN(numValue)) {
358
+ operator = 'lt';
359
+ value = numValue;
360
+ }
361
+ else {
362
+ continue;
363
+ }
364
+ }
365
+ else {
366
+ // Exact match for numbers
367
+ const numValue = parseFloat(pair.value);
368
+ if (!isNaN(numValue)) {
369
+ operator = 'eq';
370
+ value = numValue;
371
+ }
372
+ else {
373
+ continue; // Skip invalid number
374
+ }
375
+ }
376
+ }
377
+ else if (fieldType === 'boolean') {
378
+ const lowerValue = pair.value.toLowerCase();
379
+ if (lowerValue === 'true' || lowerValue === '1' || lowerValue === 'yes') {
380
+ value = true;
381
+ }
382
+ else if (lowerValue === 'false' || lowerValue === '0' || lowerValue === 'no') {
383
+ value = false;
384
+ }
385
+ else {
386
+ continue; // Skip invalid boolean
387
+ }
388
+ operator = 'eq';
389
+ }
390
+ else if (fieldType === 'date') {
391
+ // For date fields, parse and use exact match
392
+ const parsedDate = new Date(pair.value);
393
+ if (isNaN(parsedDate.getTime())) {
394
+ continue; // Skip invalid date
395
+ }
396
+ operator = 'eq';
397
+ value = parsedDate;
398
+ }
399
+ else if (fieldType === 'time') {
400
+ // Time fields can be searched with LIKE (stored as HH:MM)
401
+ operator = 'like';
402
+ value = pair.value;
403
+ }
404
+ filters.push({
405
+ id: `search-${fieldName}-${Date.now()}-${Math.random()}`,
406
+ field: fieldName,
407
+ operator,
408
+ value
409
+ });
410
+ }
411
+ return filters;
412
+ }
413
+ /**
414
+ * Set columns configuration for frontend
415
+ */
94
416
  function setColumns(fields, orderColumn, direction, searchPairs, req) {
95
417
  const columns = {};
96
- const nodeTreeColumns = [];
97
418
  let i = 1;
98
419
  for (const key of Object.keys(fields)) {
99
420
  const field = fields[key];
100
- // Check if this field is searchable
421
+ // Check if this field has search value
101
422
  const searchForThisColumn = searchPairs.find(pair => pair.column === String(i));
102
423
  const searchValue = searchForThisColumn ? searchForThisColumn.value : "";
103
424
  columns[key] = {
104
425
  ...field.config,
426
+ hasDisplayModifier: !!field.config.displayModifier,
105
427
  title: req.i18n.__(field.config.title),
106
428
  data: String(i),
107
429
  direction: String(i) === orderColumn ? direction : undefined,
108
- searchColumnValue: searchValue || undefined, // undefined if there is no search
430
+ searchColumnValue: searchValue || undefined
109
431
  };
110
- nodeTreeColumns.push({
111
- data: String(i),
112
- searchable: 'true',
113
- orderable: 'true',
114
- search: {
115
- value: searchValue,
116
- regex: false,
117
- },
118
- });
119
432
  i++;
120
433
  }
121
- return { columns, nodeTreeColumns };
434
+ return columns;
122
435
  }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * GET /adminizer/model/userfilters
3
+ * Page controller for the user filters list view.
4
+ */
5
+ export default function listUserFilters(req: ReqType, res: ResType): Promise<import("express").Response<any, Record<string, any>>>;
@@ -0,0 +1,26 @@
1
+ import { getUiTranslations } from "../lib/ui-i18n/getUiTranslations.js";
2
+ import { USER_FILTERS_UI_TRANSLATION_KEYS } from "../lib/ui-i18n/uiTranslationKeys.js";
3
+ /**
4
+ * GET /adminizer/model/userfilters
5
+ * Page controller for the user filters list view.
6
+ */
7
+ export default async function listUserFilters(req, res) {
8
+ // Get all models for the dropdown
9
+ const allModels = req.adminizer.config.models || {};
10
+ const modelsList = Object.keys(allModels).map(name => {
11
+ const config = allModels[name];
12
+ return {
13
+ name,
14
+ title: config?.title || name
15
+ };
16
+ });
17
+ return req.Inertia.render({
18
+ component: 'user-filters-list',
19
+ props: {
20
+ title: req.i18n.__('User Filters'),
21
+ models: modelsList,
22
+ apiEndpoint: `${req.adminizer.config.routePrefix}/api/all-user-filters`,
23
+ i18nPage: getUiTranslations(req, USER_FILTERS_UI_TRANSLATION_KEYS)
24
+ }
25
+ });
26
+ }
@@ -14,10 +14,10 @@ export default async function login(req, res) {
14
14
  let login = req.body.login;
15
15
  let password = req.body.password;
16
16
  let captchaSolution = req.body.captchaSolution;
17
+ const userModel = req.adminizer.modelHandler.internal("auth").get("UserAP");
17
18
  let user;
18
19
  try {
19
- // TODO refactor CRUD functions for DataAccessor usage
20
- user = await req.adminizer.modelHandler.model.get("UserAP")["_findOne"]({ login: login });
20
+ user = await userModel.findOne({ where: { login: login } });
21
21
  }
22
22
  catch (e) {
23
23
  return res.status(500).send({ error: e.message || 'Internal Server Error' });
@@ -1,18 +1,9 @@
1
1
  import { MediaManagerAdapter } from "./mediaManagerAdapter.js";
2
- import { redirectToLogin } from '../../helpers/inertiaAutHelper.js';
3
2
  export async function mediaManagerController(req, res) {
4
3
  const method = req.method.toUpperCase();
5
4
  let id = req.params.id ? req.params.id : '';
6
5
  // const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
7
6
  // await delay(1000);
8
- if (req.adminizer.config.auth.enable) {
9
- if (!req.user) {
10
- return redirectToLogin(req, res);
11
- }
12
- else if (!req.adminizer.accessRightsHelper.hasPermission(`mediaManager-${id}`, req.user)) {
13
- return res.sendStatus(403);
14
- }
15
- }
16
7
  if (!id) {
17
8
  return res.sendStatus(404);
18
9
  }
@@ -1,14 +1,8 @@
1
1
  import { ControllerHelper } from "../../helpers/controllerHelper.js";
2
2
  import { DataAccessor } from "../../lib/DataAccessor.js";
3
- import { redirectToLogin } from '../../helpers/inertiaAutHelper.js';
4
3
  export default async function edit(req, res) {
5
- if (req.adminizer.config.auth.enable) {
6
- if (!req.user) {
7
- return redirectToLogin(req, res);
8
- }
9
- }
10
- let entity = ControllerHelper.findEntityObject(req);
11
- let dataAccessor = new DataAccessor(req.adminizer, req.user, entity, "edit");
12
- let record = await entity.model.findOne({ id: req.params.id }, dataAccessor);
4
+ let modelResource = ControllerHelper.findModelResource(req);
5
+ let dataAccessor = new DataAccessor(req.adminizer, req.user, modelResource, "edit");
6
+ let record = await modelResource.model.findOne({ where: { id: req.params.id } }, dataAccessor);
13
7
  return res.redirect(`${req.adminizer.config.routePrefix}/catalog/navigation/${record.label}`);
14
8
  }
@@ -7,5 +7,5 @@ export declare class NotificationController {
7
7
  static getUserNotifications(req: ReqType, res: ResType): Promise<void>;
8
8
  static markAsRead(req: ReqType, res: ResType): Promise<void>;
9
9
  static markAllAsRead(req: ReqType, res: ResType): Promise<void>;
10
- private static checkNotifPermission;
10
+ private static checkNotifStatus;
11
11
  }