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
@@ -0,0 +1,73 @@
1
+ import { FeedService } from "../services/FeedService.js";
2
+ import { Adminizer } from "../lib/Adminizer.js";
3
+ /**
4
+ * Public feed controller — exports data by apiKey with userKey authentication
5
+ * GET /adminizer/api/feed/:apiKey.json?userKey=xxx
6
+ * GET /adminizer/api/feed/:apiKey.xml?userKey=xxx
7
+ */
8
+ export default async function feedController(req, res) {
9
+ const { apiKey, format } = req.params;
10
+ const userKey = req.query.userKey;
11
+ // Validate format
12
+ const exportFormat = (format || 'json').toLowerCase();
13
+ if (!['json', 'xml'].includes(exportFormat)) {
14
+ return res.status(400).json({ error: 'Invalid format. Supported: json, xml' });
15
+ }
16
+ if (!apiKey) {
17
+ return res.status(400).json({ error: 'apiKey is required' });
18
+ }
19
+ if (!userKey) {
20
+ return res.status(401).json({ error: 'userKey is required' });
21
+ }
22
+ try {
23
+ const feedService = new FeedService(req.adminizer);
24
+ // Find filter by apiKey
25
+ Adminizer.log.debug(`Feed: looking for filter with apiKey=${apiKey}`);
26
+ const filter = await feedService.findFilterByApiKey(apiKey);
27
+ if (!filter) {
28
+ return res.status(404).json({
29
+ error: 'Filter not found or API access is disabled',
30
+ message: 'Check your apiKey or ensure API access is enabled for this filter'
31
+ });
32
+ }
33
+ // Authenticate user by userKey
34
+ if (!req.adminizer.config.auth.enable) {
35
+ return res.status(403).json({ error: 'Auth is required for feed access' });
36
+ }
37
+ const user = await req.adminizer.modelHandler
38
+ .internal("feed")
39
+ .get("UserAP")
40
+ .findOne({ where: { apiKey: userKey } });
41
+ if (!user) {
42
+ return res.status(403).json({ error: 'Invalid userKey' });
43
+ }
44
+ Adminizer.log.info(`Feed access: filter "${filter.name}" (${filter.modelName}) by user ${user.login} via ${exportFormat}`);
45
+ Adminizer.log.debug(`Feed: filter conditions type=${typeof filter.conditions}, isArray=${Array.isArray(filter.conditions)}`);
46
+ // Generate feed based on format
47
+ if (exportFormat === 'json') {
48
+ const data = await feedService.generateJsonFeed(filter, req.adminizer, user);
49
+ res.setHeader('Content-Type', 'application/json; charset=utf-8');
50
+ // inline — отображать в браузере вместо скачивания
51
+ res.setHeader('Content-Disposition', `inline; filename="${filter.modelName}_feed.json"`);
52
+ return res.json(data);
53
+ }
54
+ else {
55
+ const xml = await feedService.generateAtomFeed(filter, req.adminizer, user);
56
+ res.setHeader('Content-Type', 'application/atom+xml; charset=utf-8');
57
+ // inline — отображать в браузере вместо скачивания
58
+ res.setHeader('Content-Disposition', `inline; filename="${filter.modelName}_feed.xml"`);
59
+ return res.send(xml);
60
+ }
61
+ }
62
+ catch (error) {
63
+ console.error('Feed export error:', error);
64
+ console.error('Stack:', error?.stack);
65
+ if (error?.cause) {
66
+ console.error('Cause:', error.cause);
67
+ }
68
+ return res.status(500).json({
69
+ error: 'Failed to generate feed',
70
+ message: error?.message || 'Unknown error'
71
+ });
72
+ }
73
+ }
@@ -1,2 +1,2 @@
1
1
  import { Adminizer } from '../../lib/Adminizer';
2
- export declare function bindFeedbackController(adminizer: Adminizer, policies: MiddlewareType[]): void;
2
+ export declare function bindFeedbackController(adminizer: Adminizer, middlewares: MiddlewareType[]): void;
@@ -1,19 +1,17 @@
1
1
  import multer from 'multer';
2
2
  import { Adminizer } from '../../lib/Adminizer.js';
3
+ import { requireAuthAPI } from '../../policies/authPolicies.js';
4
+ import { bindWithMiddlewares } from '../../system/routePolicies.js';
3
5
  const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5 MB
4
6
  const upload = multer({
5
7
  storage: multer.memoryStorage(),
6
8
  limits: { fileSize: MAX_FILE_SIZE },
7
9
  });
8
- export function bindFeedbackController(adminizer, policies) {
9
- const { app, config, policyManager } = adminizer;
10
+ export function bindFeedbackController(adminizer, middlewares) {
11
+ const { app, config } = adminizer;
10
12
  const prefix = config.routePrefix;
11
13
  const uploadMiddleware = upload.array('files', 10);
12
14
  const handler = async (req, res) => {
13
- if (adminizer.config.auth?.enable && !req.user) {
14
- res.status(401).json({ error: req.i18n.__('Unauthorized') });
15
- return;
16
- }
17
15
  uploadMiddleware(req, res, async (err) => {
18
16
  if (err) {
19
17
  if (err.code === 'LIMIT_FILE_SIZE') {
@@ -63,5 +61,5 @@ export function bindFeedbackController(adminizer, policies) {
63
61
  }
64
62
  });
65
63
  };
66
- app.post(`${prefix}/api/feedback`, ...policyManager.bindPolicies(policies, handler));
64
+ app.post(`${prefix}/api/feedback`, ...bindWithMiddlewares(adminizer, middlewares, handler, [requireAuthAPI()]));
67
65
  }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * GET /adminizer/model/:model/columns
3
+ * Returns available columns for the model (considering DataAccessor rights)
4
+ * Also returns columns for a specific filter if filterId is provided
5
+ */
6
+ export declare function getModelColumns(req: ReqType, res: ResType): Promise<ResType>;
7
+ /**
8
+ * POST /adminizer/model/:model/filter/:filterId/columns
9
+ * Update column configuration for a specific filter
10
+ */
11
+ export declare function updateFilterColumns(req: ReqType, res: ResType): Promise<ResType>;
@@ -0,0 +1,106 @@
1
+ import { ControllerHelper } from "../../helpers/controllerHelper.js";
2
+ import { DataAccessor } from "../../lib/DataAccessor.js";
3
+ import { FilterService } from "../../lib/filters/FilterService.js";
4
+ /**
5
+ * GET /adminizer/model/:model/columns
6
+ * Returns available columns for the model (considering DataAccessor rights)
7
+ * Also returns columns for a specific filter if filterId is provided
8
+ */
9
+ export async function getModelColumns(req, res) {
10
+ const modelResource = ControllerHelper.findModelResource(req);
11
+ if (!modelResource.model) {
12
+ return res.status(404).send({ error: req.i18n.__('Model not found') });
13
+ }
14
+ // Get available fields from DataAccessor (respects user permissions)
15
+ const dataAccessor = new DataAccessor(req.adminizer, req.user, modelResource, "list");
16
+ const fields = dataAccessor.getFieldsConfig();
17
+ // Convert fields to column format
18
+ const availableColumns = Object.entries(fields).map(([fieldName, field]) => {
19
+ // Get label and type from config
20
+ const config = typeof field.config === 'object' ? field.config : null;
21
+ const label = config?.title || fieldName;
22
+ const type = config?.type || 'string';
23
+ return {
24
+ fieldName,
25
+ label,
26
+ type,
27
+ order: 0
28
+ };
29
+ });
30
+ // If filterId provided, also return filter's column configuration
31
+ const filterId = req.query.filterId ? req.query.filterId.toString() : undefined;
32
+ let filterColumns = [];
33
+ if (filterId && filterId !== 'temporary') {
34
+ try {
35
+ const filterService = new FilterService(req.adminizer);
36
+ const result = await filterService.getFilterWithColumns(filterId, req.user);
37
+ if (result.filter) {
38
+ filterColumns = result.columns;
39
+ }
40
+ }
41
+ catch (err) {
42
+ // Ignore errors, return available columns without filter config
43
+ }
44
+ }
45
+ else if (filterId === 'temporary') {
46
+ // Get temporary filter columns from session
47
+ if (req.session?.temporaryFilters?.[modelResource.name]?.columns) {
48
+ const tempColumns = req.session.temporaryFilters[modelResource.name].columns;
49
+ filterColumns = tempColumns.map((col, index) => ({
50
+ id: index + 1,
51
+ filter: 'temporary',
52
+ fieldName: col.fieldName,
53
+ order: col.order !== undefined ? col.order : index
54
+ }));
55
+ }
56
+ }
57
+ return res.json({
58
+ model: modelResource.name,
59
+ availableColumns,
60
+ filterColumns: filterColumns,
61
+ hasFilterConfig: filterColumns.length > 0
62
+ });
63
+ }
64
+ /**
65
+ * POST /adminizer/model/:model/filter/:filterId/columns
66
+ * Update column configuration for a specific filter
67
+ */
68
+ export async function updateFilterColumns(req, res) {
69
+ const modelResource = ControllerHelper.findModelResource(req);
70
+ if (!modelResource.model) {
71
+ return res.status(404).send({ error: req.i18n.__('Model not found') });
72
+ }
73
+ const filterId = req.params.filterId ? String(req.params.filterId) : undefined;
74
+ if (!filterId) {
75
+ return res.status(400).send({ error: req.i18n.__('Filter ID is required') });
76
+ }
77
+ const { columns } = req.body;
78
+ if (!columns || !Array.isArray(columns)) {
79
+ return res.status(400).send({ error: req.i18n.__('Columns array is required') });
80
+ }
81
+ const filterService = new FilterService(req.adminizer);
82
+ try {
83
+ // Validate filter exists and user has access
84
+ const filter = await filterService.getFilterById(filterId, req.user);
85
+ if (!filter) {
86
+ return res.status(404).send({ error: req.i18n.__('Filter not found or access denied') });
87
+ }
88
+ // Convert frontend column format to FilterColumnAP format
89
+ const filterColumns = columns.map((col, index) => ({
90
+ fieldName: col.fieldName,
91
+ order: col.order !== undefined ? col.order : index
92
+ }));
93
+ // Save columns
94
+ const result = await filterService.updateFilterColumns(filterId, filterColumns, req.user);
95
+ return res.json({
96
+ success: true,
97
+ columns: result
98
+ });
99
+ }
100
+ catch (error) {
101
+ return res.status(500).json({
102
+ error: req.i18n.__('Failed to update filter columns'),
103
+ message: error.message
104
+ });
105
+ }
106
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Get filter fields for ANY model
3
+ *
4
+ * Takes fields from DataAccessor (merged config + access rights)
5
+ * Excludes: system fields, complex JSON fields, model-level filters.excludeFromFilters
6
+ * For Sequelize, includes simple relation filters (association/association-many + simple related fields)
7
+ *
8
+ * This controller is universal - works with any model
9
+ */
10
+ export default function filterFields(req: ReqType, res: ResType): Promise<ResType>;
@@ -0,0 +1,141 @@
1
+ import { ControllerHelper } from "../../helpers/controllerHelper.js";
2
+ import { DataAccessor } from "../../lib/DataAccessor.js";
3
+ function getFieldType(field) {
4
+ const cfg = typeof field.config === 'object' && field.config !== null ? field.config : null;
5
+ return (cfg?.type || field.model?.type || 'string');
6
+ }
7
+ function getFieldLabel(req, fieldName, field) {
8
+ const cfg = typeof field.config === 'object' && field.config !== null ? field.config : null;
9
+ return req.i18n.__(cfg?.title || fieldName) || fieldName;
10
+ }
11
+ function getFieldOptions(req, field) {
12
+ const cfg = typeof field.config === 'object' && field.config !== null ? field.config : null;
13
+ const type = (cfg?.type || field.model?.type);
14
+ const isIn = cfg?.isIn;
15
+ if ((type !== 'select' && type !== 'select-many') || !isIn) {
16
+ return undefined;
17
+ }
18
+ if (Array.isArray(isIn)) {
19
+ return isIn.map((val) => ({ value: val, label: String(val) }));
20
+ }
21
+ if (typeof isIn === 'object') {
22
+ return Object.entries(isIn).map(([value, label]) => ({
23
+ value,
24
+ label: req.i18n.__(String(label)) || String(label),
25
+ }));
26
+ }
27
+ return undefined;
28
+ }
29
+ function isSimpleFilterField(type) {
30
+ return ![
31
+ 'association',
32
+ 'association-many',
33
+ 'json',
34
+ 'jsoneditor',
35
+ 'object',
36
+ 'array',
37
+ ].includes(type);
38
+ }
39
+ /**
40
+ * Get filter fields for ANY model
41
+ *
42
+ * Takes fields from DataAccessor (merged config + access rights)
43
+ * Excludes: system fields, complex JSON fields, model-level filters.excludeFromFilters
44
+ * For Sequelize, includes simple relation filters (association/association-many + simple related fields)
45
+ *
46
+ * This controller is universal - works with any model
47
+ */
48
+ export default async function filterFields(req, res) {
49
+ const modelResource = ControllerHelper.findModelResource(req);
50
+ if (!modelResource.model) {
51
+ return res.status(404).send({ error: req.i18n.__('Model not found') });
52
+ }
53
+ // Get fields via DataAccessor (respects access rights and merged field config)
54
+ const dataAccessor = new DataAccessor(req.adminizer, req.user, modelResource, "list");
55
+ const fields = dataAccessor.getFieldsConfig() || {};
56
+ // Get filter config for this model (optional)
57
+ // Preferred: model-level `models.<Model>.filters`; fallback: legacy top-level `modelFilters.<Model>`
58
+ const filterConfig = modelResource.config?.filters ?? req.adminizer.config.modelFilters?.[modelResource.name];
59
+ const filterFields = [];
60
+ for (const [fieldName, field] of Object.entries(fields)) {
61
+ const type = getFieldType(field);
62
+ const cfg = typeof field.config === 'object' && field.config !== null ? field.config : null;
63
+ const customFilterHandlerId = cfg?.customFilter?.handlerId
64
+ ? String(cfg.customFilter.handlerId)
65
+ : undefined;
66
+ const customFilterHandler = customFilterHandlerId
67
+ ? req.adminizer.customFilterHandler?.get(customFilterHandlerId)
68
+ : undefined;
69
+ const customFilter = customFilterHandlerId
70
+ ? {
71
+ handlerId: customFilterHandlerId,
72
+ conditionLabel: customFilterHandler?.name
73
+ || (cfg?.customFilter?.label ? String(cfg.customFilter.label) : undefined)
74
+ || customFilterHandlerId,
75
+ inputConfig: customFilterHandler?.inputConfig,
76
+ }
77
+ : null;
78
+ // Skip model-specific excluded fields
79
+ if (filterConfig?.excludeFromFilters?.includes(fieldName)) {
80
+ continue;
81
+ }
82
+ // Skip system fields (universal rule for all models)
83
+ if (fieldName === 'id' || fieldName === 'ownerId' || fieldName === 'updatedAt') {
84
+ continue;
85
+ }
86
+ const isRelationField = type === 'association' || type === 'association-many';
87
+ // Base (non-relation) fields
88
+ if (!isRelationField) {
89
+ if (!isSimpleFilterField(type) && !customFilter) {
90
+ continue;
91
+ }
92
+ filterFields.push({
93
+ id: fieldName,
94
+ label: getFieldLabel(req, fieldName, field),
95
+ type,
96
+ isCustomFilter: Boolean(customFilter),
97
+ customFilterHandlerId: customFilter?.handlerId,
98
+ customFilterConditionLabel: customFilter?.conditionLabel,
99
+ customFilterInputConfig: customFilter?.inputConfig,
100
+ required: Boolean((typeof field.config === 'object' && field.config !== null ? field.config.required : false)),
101
+ options: getFieldOptions(req, field),
102
+ });
103
+ continue;
104
+ }
105
+ const ormType = req.adminizer.ormAdapters?.[0]?.ormType;
106
+ const isSequelize = ormType === 'sequelize';
107
+ // Relations in filter UI are supported only for Sequelize
108
+ if (!isSequelize) {
109
+ continue;
110
+ }
111
+ // Include relation with only simple fields of related model
112
+ const relationFields = Object.entries(field.populated || {})
113
+ .map(([relatedFieldName, relatedField]) => {
114
+ const relatedType = getFieldType(relatedField);
115
+ if (!isSimpleFilterField(relatedType)) {
116
+ return null;
117
+ }
118
+ return {
119
+ id: relatedFieldName,
120
+ label: getFieldLabel(req, relatedFieldName, relatedField),
121
+ type: relatedType,
122
+ options: getFieldOptions(req, relatedField),
123
+ };
124
+ })
125
+ .filter((item) => !!item);
126
+ if (relationFields.length === 0) {
127
+ continue;
128
+ }
129
+ filterFields.push({
130
+ id: fieldName,
131
+ label: getFieldLabel(req, fieldName, field),
132
+ type,
133
+ isRelation: true,
134
+ relationFields,
135
+ });
136
+ }
137
+ return res.json({
138
+ model: modelResource.name,
139
+ fields: filterFields,
140
+ });
141
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * GET /adminizer/api/all-user-filters
3
+ * Returns all filters accessible to the current user across all models.
4
+ * Supports search by name, filter by modelName, and pagination.
5
+ */
6
+ export declare function getAllUserFilters(req: ReqType, res: ResType): Promise<ResType>;
@@ -0,0 +1,115 @@
1
+ import { FilterService } from "../../lib/filters/FilterService.js";
2
+ import { Adminizer } from "../../lib/Adminizer.js";
3
+ /**
4
+ * GET /adminizer/api/all-user-filters
5
+ * Returns all filters accessible to the current user across all models.
6
+ * Supports search by name, filter by modelName, and pagination.
7
+ */
8
+ export async function getAllUserFilters(req, res) {
9
+ const filterService = new FilterService(req.adminizer);
10
+ const userModel = req.adminizer.modelHandler.model.get('userap');
11
+ const groupModel = req.adminizer.modelHandler.model.get('groupap');
12
+ // Cache for group names: { groupId: groupName }
13
+ const groupNamesCache = {};
14
+ const getGroupName = async (groupId) => {
15
+ if (groupNamesCache[groupId])
16
+ return groupNamesCache[groupId];
17
+ try {
18
+ const group = await groupModel?.['_findOne']({ where: { id: groupId } });
19
+ if (group) {
20
+ groupNamesCache[groupId] = group.name || String(groupId);
21
+ return groupNamesCache[groupId];
22
+ }
23
+ }
24
+ catch (e) { /* ignore */ }
25
+ groupNamesCache[groupId] = String(groupId);
26
+ return groupNamesCache[groupId];
27
+ };
28
+ // Extract query parameters
29
+ const search = typeof req.query.search === 'string' ? req.query.search : undefined;
30
+ const modelNameFilter = typeof req.query.modelName === 'string' ? req.query.modelName : undefined;
31
+ const offset = parseInt(String(req.query.offset || 0), 10) || 0;
32
+ const limit = parseInt(String(req.query.limit || 50), 10) || 50;
33
+ // Get all models from config
34
+ const allModels = req.adminizer.config.models || {};
35
+ const modelNames = Object.keys(allModels);
36
+ // Determine which models to query
37
+ const modelsToQuery = modelNameFilter
38
+ ? modelNames.filter(name => name === modelNameFilter)
39
+ : modelNames;
40
+ // Collect all filters from all models
41
+ const allFilters = [];
42
+ for (const modelName of modelsToQuery) {
43
+ try {
44
+ const filters = await filterService.getFiltersForModel(modelName, req.user, {
45
+ includePublic: true,
46
+ includeSystem: false
47
+ });
48
+ const modelConfig = allModels[modelName];
49
+ const modelTitle = modelConfig?.title || modelName;
50
+ for (const filter of filters) {
51
+ // Load owner info
52
+ let ownerName;
53
+ if (userModel && filter.ownerId) {
54
+ try {
55
+ const owner = await userModel['_findOne']({ where: { id: filter.ownerId } });
56
+ if (owner) {
57
+ ownerName = owner.fullName || owner.login || String(owner.id);
58
+ }
59
+ }
60
+ catch (e) {
61
+ // Ignore owner load errors
62
+ }
63
+ }
64
+ // Load group names for 'groups' visibility
65
+ let groupNames;
66
+ if (filter.visibility === 'groups' && filter.groupIds && filter.groupIds.length > 0) {
67
+ groupNames = [];
68
+ for (const gid of filter.groupIds) {
69
+ const gName = await getGroupName(gid);
70
+ groupNames.push(gName);
71
+ }
72
+ }
73
+ allFilters.push({
74
+ id: filter.id,
75
+ name: filter.name,
76
+ modelName: filter.modelName,
77
+ modelTitle,
78
+ visibility: filter.visibility,
79
+ apiEnabled: filter.apiEnabled === true,
80
+ ownerId: filter.ownerId,
81
+ ownerName,
82
+ isOwner: filter.ownerId === req.user?.id,
83
+ groupNames
84
+ });
85
+ }
86
+ }
87
+ catch (e) {
88
+ Adminizer.log.debug(`Error loading filters for model ${modelName}:`, e);
89
+ }
90
+ }
91
+ // Apply search filter (by name, case-insensitive)
92
+ let filteredFilters = allFilters;
93
+ if (search) {
94
+ const searchLower = search.toLowerCase();
95
+ filteredFilters = allFilters.filter(f => f.name.toLowerCase().includes(searchLower));
96
+ }
97
+ // Calculate pagination
98
+ const total = filteredFilters.length;
99
+ const paginatedFilters = filteredFilters.slice(offset, offset + limit);
100
+ const hasMore = offset + limit < total;
101
+ // Build models list for frontend dropdown
102
+ const modelsList = modelNames.map(name => {
103
+ const config = allModels[name];
104
+ return {
105
+ name,
106
+ title: config?.title || name
107
+ };
108
+ });
109
+ return res.json({
110
+ filters: paginatedFilters,
111
+ models: modelsList,
112
+ total,
113
+ hasMore
114
+ });
115
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * GET /adminizer/groups
3
+ * Returns list of all groups (for filter visibility settings)
4
+ */
5
+ export declare function getAllGroups(req: ReqType, res: ResType): Promise<ResType>;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * GET /adminizer/groups
3
+ * Returns list of all groups (for filter visibility settings)
4
+ */
5
+ export async function getAllGroups(req, res) {
6
+ try {
7
+ const groupModel = req.adminizer.modelHandler.model.get('groupap');
8
+ if (!groupModel) {
9
+ return res.status(500).send({ error: req.i18n.__('GroupAP model not found') });
10
+ }
11
+ const groups = await groupModel['_find']({});
12
+ return res.json({
13
+ groups: groups.map((g) => ({
14
+ id: g.id,
15
+ name: g.name,
16
+ description: g.description || ''
17
+ }))
18
+ });
19
+ }
20
+ catch (error) {
21
+ return res.status(500).json({
22
+ error: req.i18n.__('Failed to load groups'),
23
+ message: error.message
24
+ });
25
+ }
26
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * POST /adminizer/model/:model/filter/apply
3
+ * Apply temporary filter (without saving) - stores filter in session keyed by model name
4
+ */
5
+ export declare function applyTemporaryFilter(req: ReqType, res: ResType): Promise<ResType>;
6
+ /**
7
+ * GET /adminizer/model/:model/filter/temporary
8
+ * Returns temporary filter from server session.
9
+ */
10
+ export declare function getTemporaryFilter(req: ReqType, res: ResType): Promise<ResType>;
11
+ /**
12
+ * GET /adminizer/model/:model/saved-filters
13
+ * Returns list of saved filters for the model
14
+ */
15
+ export declare function getSavedFilters(req: ReqType, res: ResType): Promise<ResType>;
16
+ /**
17
+ * POST /adminizer/model/:model/filter
18
+ * Create or update a saved filter
19
+ */
20
+ export declare function saveFilter(req: ReqType, res: ResType): Promise<ResType>;
21
+ /**
22
+ * DELETE /adminizer/model/:model/filter/:id
23
+ * Delete a saved filter
24
+ */
25
+ export declare function deleteFilter(req: ReqType, res: ResType): Promise<ResType>;