dmart 1.4.40.post8__py3-none-any.whl

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 (489) hide show
  1. dmart/__init__.py +7 -0
  2. dmart/alembic/README +1 -0
  3. dmart/alembic/__init__.py +0 -0
  4. dmart/alembic/env.py +91 -0
  5. dmart/alembic/notes.txt +11 -0
  6. dmart/alembic/script.py.mako +28 -0
  7. dmart/alembic/scripts/__init__.py +0 -0
  8. dmart/alembic/scripts/calculate_checksums.py +77 -0
  9. dmart/alembic/scripts/migration_f7a4949eed19.py +28 -0
  10. dmart/alembic/versions/0f3d2b1a7c21_add_authz_materialized_views.py +87 -0
  11. dmart/alembic/versions/10d2041b94d4_last_checksum_history.py +62 -0
  12. dmart/alembic/versions/1cf4e1ee3cb8_ext_permission_with_filter_fields_values.py +33 -0
  13. dmart/alembic/versions/26bfe19b49d4_rm_failedloginattempts.py +42 -0
  14. dmart/alembic/versions/3c8bca2219cc_add_otp_table.py +38 -0
  15. dmart/alembic/versions/6675fd9dfe42_remove_unique_from_sessions_table.py +36 -0
  16. dmart/alembic/versions/71bc1df82e6a_adding_user_last_login_at.py +43 -0
  17. dmart/alembic/versions/74288ccbd3b5_initial.py +264 -0
  18. dmart/alembic/versions/7520a89a8467_rm_activesession_table.py +39 -0
  19. dmart/alembic/versions/848b623755a4_make_created_nd_updated_at_required.py +138 -0
  20. dmart/alembic/versions/8640dcbebf85_add_notes_to_users.py +32 -0
  21. dmart/alembic/versions/91c94250232a_adding_fk_on_owner_shortname.py +104 -0
  22. dmart/alembic/versions/98ecd6f56f9a_ext_meta_with_owner_group_shortname.py +66 -0
  23. dmart/alembic/versions/9aae9138c4ef_indexing_created_at_updated_at.py +80 -0
  24. dmart/alembic/versions/__init__.py +0 -0
  25. dmart/alembic/versions/b53f916b3f6d_json_to_jsonb.py +492 -0
  26. dmart/alembic/versions/eb5f1ec65156_adding_user_locked_to_device.py +36 -0
  27. dmart/alembic/versions/f7a4949eed19_adding_query_policies_to_meta.py +60 -0
  28. dmart/alembic.ini +117 -0
  29. dmart/api/__init__.py +0 -0
  30. dmart/api/info/__init__.py +0 -0
  31. dmart/api/info/router.py +109 -0
  32. dmart/api/managed/__init__.py +0 -0
  33. dmart/api/managed/router.py +1541 -0
  34. dmart/api/managed/utils.py +1879 -0
  35. dmart/api/public/__init__.py +0 -0
  36. dmart/api/public/router.py +758 -0
  37. dmart/api/qr/__init__.py +0 -0
  38. dmart/api/qr/router.py +108 -0
  39. dmart/api/user/__init__.py +0 -0
  40. dmart/api/user/model/__init__.py +0 -0
  41. dmart/api/user/model/errors.py +14 -0
  42. dmart/api/user/model/requests.py +165 -0
  43. dmart/api/user/model/responses.py +11 -0
  44. dmart/api/user/router.py +1413 -0
  45. dmart/api/user/service.py +270 -0
  46. dmart/bundler.py +52 -0
  47. dmart/cli.py +1133 -0
  48. dmart/config/__init__.py +0 -0
  49. dmart/config/channels.json +11 -0
  50. dmart/config/notification.json +17 -0
  51. dmart/config.env.sample +27 -0
  52. dmart/config.ini.sample +7 -0
  53. dmart/conftest.py +13 -0
  54. dmart/curl.sh +196 -0
  55. dmart/cxb/__init__.py +0 -0
  56. dmart/cxb/assets/@codemirror-Rn7_6DkE.js +10 -0
  57. dmart/cxb/assets/@edraj-CS4NwVbD.js +1 -0
  58. dmart/cxb/assets/@floating-ui-BwwcF-xh.js +1 -0
  59. dmart/cxb/assets/@formatjs-yKEsAtjs.js +1 -0
  60. dmart/cxb/assets/@fortawesome-DRW1UCdr.js +9 -0
  61. dmart/cxb/assets/@jsonquerylang-laKNoFFq.js +12 -0
  62. dmart/cxb/assets/@lezer-za4Q-8Ew.js +1 -0
  63. dmart/cxb/assets/@marijn-DXwl3gUT.js +1 -0
  64. dmart/cxb/assets/@popperjs-l0sNRNKZ.js +1 -0
  65. dmart/cxb/assets/@replit--ERk53eB.js +1 -0
  66. dmart/cxb/assets/@roxi-CGMFK4i8.js +6 -0
  67. dmart/cxb/assets/@typewriter-cCzskkIv.js +17 -0
  68. dmart/cxb/assets/@zerodevx-BlBZjKxu.js +1 -0
  69. dmart/cxb/assets/@zerodevx-CVEpe6WZ.css +1 -0
  70. dmart/cxb/assets/BreadCrumbLite-DAhOx38v.js +1 -0
  71. dmart/cxb/assets/EntryRenderer-CCqV8Rkg.js +32 -0
  72. dmart/cxb/assets/EntryRenderer-DXytdFp9.css +1 -0
  73. dmart/cxb/assets/ListView-BQelo7vZ.js +16 -0
  74. dmart/cxb/assets/ListView-U8of-_c-.css +1 -0
  75. dmart/cxb/assets/Prism--hMplq-p.js +3 -0
  76. dmart/cxb/assets/Prism-Uh6uStUw.css +1 -0
  77. dmart/cxb/assets/Table2Cols-BsbwicQm.js +1 -0
  78. dmart/cxb/assets/_..-BvT6vdHa.css +1 -0
  79. dmart/cxb/assets/_...404_-fuLH_rX9.js +2 -0
  80. dmart/cxb/assets/_...fallback_-Ba_NLmAE.js +1 -0
  81. dmart/cxb/assets/_module-3HrtKAWo.js +3 -0
  82. dmart/cxb/assets/_module-DFKFq0AM.js +4 -0
  83. dmart/cxb/assets/_module-Dgq0ZVtz.js +1 -0
  84. dmart/cxb/assets/ajv-Cpj98o6Y.js +1 -0
  85. dmart/cxb/assets/axios-CG2WSiiR.js +6 -0
  86. dmart/cxb/assets/clsx-B-dksMZM.js +1 -0
  87. dmart/cxb/assets/codemirror-wrapped-line-indent-DPhKvljI.js +1 -0
  88. dmart/cxb/assets/compare-C3AjiGFR.js +1 -0
  89. dmart/cxb/assets/compute-scroll-into-view-Bl8rNFhg.js +1 -0
  90. dmart/cxb/assets/consolite-DlCuI0F9.js +1 -0
  91. dmart/cxb/assets/crelt-C8TCjufn.js +1 -0
  92. dmart/cxb/assets/date-fns-l0sNRNKZ.js +1 -0
  93. dmart/cxb/assets/deepmerge-rn4rBaHU.js +1 -0
  94. dmart/cxb/assets/dmart_services-AL6-IdDE.js +1 -0
  95. dmart/cxb/assets/downloadFile-D08i0YDh.js +1 -0
  96. dmart/cxb/assets/easy-signal-BiPFIK3O.js +1 -0
  97. dmart/cxb/assets/esm-env-rsSWfq8L.js +1 -0
  98. dmart/cxb/assets/export-OF_rTiXu.js +1 -0
  99. dmart/cxb/assets/fast-deep-equal-l0sNRNKZ.js +1 -0
  100. dmart/cxb/assets/fast-diff-C-IidNf4.js +1 -0
  101. dmart/cxb/assets/fast-uri-l0sNRNKZ.js +1 -0
  102. dmart/cxb/assets/flowbite-svelte-BLvjb-sa.js +1 -0
  103. dmart/cxb/assets/flowbite-svelte-CD54FDqW.css +1 -0
  104. dmart/cxb/assets/flowbite-svelte-icons-BI8GVhw_.js +1 -0
  105. dmart/cxb/assets/github-slugger-CQ4oX9Ud.js +1 -0
  106. dmart/cxb/assets/global-igKv-1g9.js +1 -0
  107. dmart/cxb/assets/hookar-BMRD9G9H.js +1 -0
  108. dmart/cxb/assets/immutable-json-patch-DtRO2E_S.js +1 -0
  109. dmart/cxb/assets/import-1vE3gBat.js +1 -0
  110. dmart/cxb/assets/index-B-eTh-ZX.js +1 -0
  111. dmart/cxb/assets/index-BSsK-X71.js +1 -0
  112. dmart/cxb/assets/index-BVyxzKtH.js +1 -0
  113. dmart/cxb/assets/index-BdeNM69f.js +1 -0
  114. dmart/cxb/assets/index-CC-A1ipE.js +1 -0
  115. dmart/cxb/assets/index-CQohGiYB.js +1 -0
  116. dmart/cxb/assets/index-ChjnkpdZ.js +4 -0
  117. dmart/cxb/assets/index-DLP7csA4.js +1 -0
  118. dmart/cxb/assets/index-DTfhnhwd.js +1 -0
  119. dmart/cxb/assets/index-DdXRK7n9.js +2 -0
  120. dmart/cxb/assets/index-DtiCmB4o.js +1 -0
  121. dmart/cxb/assets/index-NBrXBlLA.css +2 -0
  122. dmart/cxb/assets/index-X1uNehO7.js +1 -0
  123. dmart/cxb/assets/index-nrQW6Nrr.js +1 -0
  124. dmart/cxb/assets/info-B986lRiM.js +1 -0
  125. dmart/cxb/assets/intl-messageformat-Dc5UU-HB.js +3 -0
  126. dmart/cxb/assets/jmespath-l0sNRNKZ.js +1 -0
  127. dmart/cxb/assets/json-schema-traverse-l0sNRNKZ.js +1 -0
  128. dmart/cxb/assets/json-source-map-DRgZidqy.js +5 -0
  129. dmart/cxb/assets/jsonpath-plus-l0sNRNKZ.js +1 -0
  130. dmart/cxb/assets/jsonrepair-B30Dx381.js +8 -0
  131. dmart/cxb/assets/lodash-es-DZVAA2ox.js +1 -0
  132. dmart/cxb/assets/marked-DKjyhwJX.js +56 -0
  133. dmart/cxb/assets/marked-gfm-heading-id-U5zO829x.js +2 -0
  134. dmart/cxb/assets/marked-mangle-CDMeiHC6.js +1 -0
  135. dmart/cxb/assets/memoize-one-BdPwpGay.js +1 -0
  136. dmart/cxb/assets/natural-compare-lite-Bg2Xcf-o.js +7 -0
  137. dmart/cxb/assets/pagination-svelte-D5CyoiE_.js +13 -0
  138. dmart/cxb/assets/pagination-svelte-v10nAbbM.css +1 -0
  139. dmart/cxb/assets/plantuml-encoder-C47mzt9T.js +1 -0
  140. dmart/cxb/assets/prismjs-DTUiLGJu.js +9 -0
  141. dmart/cxb/assets/profile-BUf-tKMe.js +1 -0
  142. dmart/cxb/assets/query-CNmXTsgf.js +1 -0
  143. dmart/cxb/assets/queryHelpers-C9iBWwqe.js +1 -0
  144. dmart/cxb/assets/scroll-into-view-if-needed-KR58zyjF.js +1 -0
  145. dmart/cxb/assets/spaces-0oyGvpii.js +1 -0
  146. dmart/cxb/assets/style-mod-Bs6eFhZE.js +3 -0
  147. dmart/cxb/assets/svelte-B2XmcTi_.js +4 -0
  148. dmart/cxb/assets/svelte-awesome-COLlx0DN.css +1 -0
  149. dmart/cxb/assets/svelte-awesome-DhnMA6Q_.js +1 -0
  150. dmart/cxb/assets/svelte-datatables-net-CY7LBj6I.js +1 -0
  151. dmart/cxb/assets/svelte-floating-ui-BlS3sOAQ.js +1 -0
  152. dmart/cxb/assets/svelte-i18n-CT2KkQaN.js +3 -0
  153. dmart/cxb/assets/svelte-jsoneditor-BzfX6Usi.css +1 -0
  154. dmart/cxb/assets/svelte-jsoneditor-CUGSvWId.js +25 -0
  155. dmart/cxb/assets/svelte-select-CegQKzqH.css +1 -0
  156. dmart/cxb/assets/svelte-select-CjHAt_85.js +6 -0
  157. dmart/cxb/assets/tailwind-merge-CJvxXMcu.js +1 -0
  158. dmart/cxb/assets/tailwind-variants-Cj20BoQ3.js +1 -0
  159. dmart/cxb/assets/toast-B9WDyfyI.js +1 -0
  160. dmart/cxb/assets/tslib-pJfR_DrR.js +1 -0
  161. dmart/cxb/assets/typewriter-editor-DkTVIJdm.js +25 -0
  162. dmart/cxb/assets/user-DeK_NB5v.js +1 -0
  163. dmart/cxb/assets/vanilla-picker-l5rcX3cq.js +8 -0
  164. dmart/cxb/assets/w3c-keyname-Vcq4gwWv.js +1 -0
  165. dmart/cxb/config.json +11 -0
  166. dmart/cxb/config.sample.json +11 -0
  167. dmart/cxb/favicon.ico +0 -0
  168. dmart/cxb/favicon.png +0 -0
  169. dmart/cxb/index.html +28 -0
  170. dmart/data_adapters/__init__.py +0 -0
  171. dmart/data_adapters/adapter.py +16 -0
  172. dmart/data_adapters/base_data_adapter.py +467 -0
  173. dmart/data_adapters/file/__init__.py +0 -0
  174. dmart/data_adapters/file/adapter.py +2043 -0
  175. dmart/data_adapters/file/adapter_helpers.py +1013 -0
  176. dmart/data_adapters/file/archive.py +150 -0
  177. dmart/data_adapters/file/create_index.py +331 -0
  178. dmart/data_adapters/file/create_users_folders.py +52 -0
  179. dmart/data_adapters/file/custom_validations.py +68 -0
  180. dmart/data_adapters/file/drop_index.py +40 -0
  181. dmart/data_adapters/file/health_check.py +560 -0
  182. dmart/data_adapters/file/redis_services.py +1110 -0
  183. dmart/data_adapters/helpers.py +27 -0
  184. dmart/data_adapters/sql/__init__.py +0 -0
  185. dmart/data_adapters/sql/adapter.py +3218 -0
  186. dmart/data_adapters/sql/adapter_helpers.py +491 -0
  187. dmart/data_adapters/sql/create_tables.py +451 -0
  188. dmart/data_adapters/sql/create_users_folders.py +53 -0
  189. dmart/data_adapters/sql/db_to_json_migration.py +485 -0
  190. dmart/data_adapters/sql/health_check_sql.py +232 -0
  191. dmart/data_adapters/sql/json_to_db_migration.py +454 -0
  192. dmart/data_adapters/sql/update_query_policies.py +101 -0
  193. dmart/data_generator.py +81 -0
  194. dmart/dmart.py +761 -0
  195. dmart/get_settings.py +7 -0
  196. dmart/hypercorn_config.toml +3 -0
  197. dmart/info.json +1 -0
  198. dmart/languages/__init__.py +0 -0
  199. dmart/languages/arabic.json +15 -0
  200. dmart/languages/english.json +16 -0
  201. dmart/languages/kurdish.json +14 -0
  202. dmart/languages/loader.py +12 -0
  203. dmart/login_creds.sh +7 -0
  204. dmart/login_creds.sh.sample +7 -0
  205. dmart/main.py +563 -0
  206. dmart/manifest.sh +12 -0
  207. dmart/migrate.py +24 -0
  208. dmart/models/__init__.py +0 -0
  209. dmart/models/api.py +203 -0
  210. dmart/models/core.py +597 -0
  211. dmart/models/enums.py +255 -0
  212. dmart/password_gen.py +8 -0
  213. dmart/plugins/__init__.py +0 -0
  214. dmart/plugins/action_log/__init__.py +0 -0
  215. dmart/plugins/action_log/config.json +13 -0
  216. dmart/plugins/action_log/plugin.py +121 -0
  217. dmart/plugins/admin_notification_sender/__init__.py +0 -0
  218. dmart/plugins/admin_notification_sender/config.json +13 -0
  219. dmart/plugins/admin_notification_sender/plugin.py +124 -0
  220. dmart/plugins/ldap_manager/__init__.py +0 -0
  221. dmart/plugins/ldap_manager/config.json +12 -0
  222. dmart/plugins/ldap_manager/dmart.schema +146 -0
  223. dmart/plugins/ldap_manager/plugin.py +100 -0
  224. dmart/plugins/ldap_manager/slapd.conf +53 -0
  225. dmart/plugins/local_notification/__init__.py +0 -0
  226. dmart/plugins/local_notification/config.json +13 -0
  227. dmart/plugins/local_notification/plugin.py +123 -0
  228. dmart/plugins/realtime_updates_notifier/__init__.py +0 -0
  229. dmart/plugins/realtime_updates_notifier/config.json +12 -0
  230. dmart/plugins/realtime_updates_notifier/plugin.py +58 -0
  231. dmart/plugins/redis_db_update/__init__.py +0 -0
  232. dmart/plugins/redis_db_update/config.json +13 -0
  233. dmart/plugins/redis_db_update/plugin.py +188 -0
  234. dmart/plugins/resource_folders_creation/__init__.py +0 -0
  235. dmart/plugins/resource_folders_creation/config.json +12 -0
  236. dmart/plugins/resource_folders_creation/plugin.py +81 -0
  237. dmart/plugins/system_notification_sender/__init__.py +0 -0
  238. dmart/plugins/system_notification_sender/config.json +13 -0
  239. dmart/plugins/system_notification_sender/plugin.py +188 -0
  240. dmart/plugins/update_access_controls/__init__.py +0 -0
  241. dmart/plugins/update_access_controls/config.json +12 -0
  242. dmart/plugins/update_access_controls/plugin.py +9 -0
  243. dmart/publish.sh +57 -0
  244. dmart/pylint.sh +16 -0
  245. dmart/pyrightconfig.json +7 -0
  246. dmart/redis_connections.sh +13 -0
  247. dmart/reload.sh +56 -0
  248. dmart/run.sh +3 -0
  249. dmart/run_notification_campaign.py +85 -0
  250. dmart/sample/spaces/applications/.dm/meta.space.json +30 -0
  251. dmart/sample/spaces/applications/api/.dm/meta.folder.json +1 -0
  252. dmart/sample/spaces/applications/api/.dm/query_all_applications/meta.content.json +1 -0
  253. dmart/sample/spaces/applications/api/.dm/test_by_saad/attachments.media/meta.warframe.json +1 -0
  254. dmart/sample/spaces/applications/api/.dm/test_by_saad/attachments.media/warframe.png +0 -0
  255. dmart/sample/spaces/applications/api/.dm/test_by_saad/meta.content.json +1 -0
  256. dmart/sample/spaces/applications/api/.dm/user_profile/meta.content.json +1 -0
  257. dmart/sample/spaces/applications/api/applications/.dm/create_log/meta.content.json +1 -0
  258. dmart/sample/spaces/applications/api/applications/.dm/create_public_logs/meta.content.json +1 -0
  259. dmart/sample/spaces/applications/api/applications/.dm/meta.folder.json +1 -0
  260. dmart/sample/spaces/applications/api/applications/.dm/query_all_translated_data/meta.content.json +1 -0
  261. dmart/sample/spaces/applications/api/applications/.dm/query_logs/meta.content.json +1 -0
  262. dmart/sample/spaces/applications/api/applications/.dm/query_translated_enums/meta.content.json +1 -0
  263. dmart/sample/spaces/applications/api/applications/.dm/query_translated_others/meta.content.json +1 -0
  264. dmart/sample/spaces/applications/api/applications/.dm/query_translated_resolution/meta.content.json +1 -0
  265. dmart/sample/spaces/applications/api/applications/create_log.json +1 -0
  266. dmart/sample/spaces/applications/api/applications/create_public_logs.json +1 -0
  267. dmart/sample/spaces/applications/api/applications/query_all_translated_data.json +1 -0
  268. dmart/sample/spaces/applications/api/applications/query_logs.json +1 -0
  269. dmart/sample/spaces/applications/api/applications/query_translated_enums.json +1 -0
  270. dmart/sample/spaces/applications/api/applications/query_translated_others.json +1 -0
  271. dmart/sample/spaces/applications/api/applications/query_translated_resolution.json +1 -0
  272. dmart/sample/spaces/applications/api/applications.json +1 -0
  273. dmart/sample/spaces/applications/api/management/.dm/create_subaccount/meta.content.json +1 -0
  274. dmart/sample/spaces/applications/api/management/.dm/meta.folder.json +1 -0
  275. dmart/sample/spaces/applications/api/management/.dm/update_password/meta.content.json +1 -0
  276. dmart/sample/spaces/applications/api/management/create_subaccount.json +53 -0
  277. dmart/sample/spaces/applications/api/management/update_password.json +1 -0
  278. dmart/sample/spaces/applications/api/management.json +1 -0
  279. dmart/sample/spaces/applications/api/query_all_applications.json +15 -0
  280. dmart/sample/spaces/applications/api/test_by_saad.json +1 -0
  281. dmart/sample/spaces/applications/api/user/.dm/meta.folder.json +1 -0
  282. dmart/sample/spaces/applications/api/user/.dm/test_by_saad/meta.content.json +1 -0
  283. dmart/sample/spaces/applications/api/user/.dm/user_profile/meta.content.json +1 -0
  284. dmart/sample/spaces/applications/api/user/test_by_saad.json +1 -0
  285. dmart/sample/spaces/applications/api/user/user_profile.json +1 -0
  286. dmart/sample/spaces/applications/api/user_profile.json +1 -0
  287. dmart/sample/spaces/applications/api.json +1 -0
  288. dmart/sample/spaces/applications/collections/.dm/meta.folder.json +19 -0
  289. dmart/sample/spaces/applications/collections.json +1 -0
  290. dmart/sample/spaces/applications/configurations/.dm/meta.folder.json +1 -0
  291. dmart/sample/spaces/applications/configurations/time_out.json +1 -0
  292. dmart/sample/spaces/applications/configurations.json +19 -0
  293. dmart/sample/spaces/applications/errors.json +1 -0
  294. dmart/sample/spaces/applications/logs/.dm/meta.folder.json +1 -0
  295. dmart/sample/spaces/applications/logs.json +1 -0
  296. dmart/sample/spaces/applications/queries/.dm/meta.folder.json +1 -0
  297. dmart/sample/spaces/applications/queries/.dm/order/meta.content.json +1 -0
  298. dmart/sample/spaces/applications/queries/order.json +1 -0
  299. dmart/sample/spaces/applications/queries.json +1 -0
  300. dmart/sample/spaces/applications/schema/.dm/api/meta.schema.json +1 -0
  301. dmart/sample/spaces/applications/schema/.dm/configuration/meta.schema.json +1 -0
  302. dmart/sample/spaces/applications/schema/.dm/error/meta.schema.json +1 -0
  303. dmart/sample/spaces/applications/schema/.dm/log/meta.schema.json +1 -0
  304. dmart/sample/spaces/applications/schema/.dm/meta.folder.json +1 -0
  305. dmart/sample/spaces/applications/schema/.dm/query/meta.schema.json +16 -0
  306. dmart/sample/spaces/applications/schema/.dm/translation/meta.schema.json +1 -0
  307. dmart/sample/spaces/applications/schema/api.json +28 -0
  308. dmart/sample/spaces/applications/schema/configuration.json +1 -0
  309. dmart/sample/spaces/applications/schema/error.json +43 -0
  310. dmart/sample/spaces/applications/schema/log.json +1 -0
  311. dmart/sample/spaces/applications/schema/query.json +118 -0
  312. dmart/sample/spaces/applications/schema/translation.json +26 -0
  313. dmart/sample/spaces/applications/schema.json +1 -0
  314. dmart/sample/spaces/applications/translations/.dm/meta.folder.json +1 -0
  315. dmart/sample/spaces/applications/translations.json +1 -0
  316. dmart/sample/spaces/archive/.dm/meta.space.json +27 -0
  317. dmart/sample/spaces/custom_plugins/dummy/__pycache__/plugin.cpython-314.pyc +0 -0
  318. dmart/sample/spaces/custom_plugins/dummy/config.json +28 -0
  319. dmart/sample/spaces/custom_plugins/dummy/plugin.py +6 -0
  320. dmart/sample/spaces/custom_plugins/missed_entry/config.json +12 -0
  321. dmart/sample/spaces/custom_plugins/missed_entry/plugin.py +119 -0
  322. dmart/sample/spaces/custom_plugins/own_changed_notification/__pycache__/plugin.cpython-314.pyc +0 -0
  323. dmart/sample/spaces/custom_plugins/own_changed_notification/config.json +12 -0
  324. dmart/sample/spaces/custom_plugins/own_changed_notification/plugin.py +65 -0
  325. dmart/sample/spaces/custom_plugins/reports_stats/config.json +14 -0
  326. dmart/sample/spaces/custom_plugins/reports_stats/plugin.py +82 -0
  327. dmart/sample/spaces/custom_plugins/system_notification_sender/config.json +22 -0
  328. dmart/sample/spaces/custom_plugins/system_notification_sender/notification.py +268 -0
  329. dmart/sample/spaces/custom_plugins/system_notification_sender/plugin.py +98 -0
  330. dmart/sample/spaces/management/.dm/events.jsonl +32 -0
  331. dmart/sample/spaces/management/.dm/meta.space.json +48 -0
  332. dmart/sample/spaces/management/.dm/notifications/attachments.view.json/admin.json +36 -0
  333. dmart/sample/spaces/management/.dm/notifications/attachments.view.json/meta.admin.json +1 -0
  334. dmart/sample/spaces/management/.dm/notifications/attachments.view.json/meta.system.json +1 -0
  335. dmart/sample/spaces/management/.dm/notifications/attachments.view.json/system.json +32 -0
  336. dmart/sample/spaces/management/collections/.dm/meta.folder.json +1 -0
  337. dmart/sample/spaces/management/collections.json +1 -0
  338. dmart/sample/spaces/management/groups/.dm/meta.folder.json +1 -0
  339. dmart/sample/spaces/management/groups.json +1 -0
  340. dmart/sample/spaces/management/health_check/.dm/meta.folder.json +1 -0
  341. dmart/sample/spaces/management/health_check.json +1 -0
  342. dmart/sample/spaces/management/notifications/.dm/meta.folder.json +1 -0
  343. dmart/sample/spaces/management/notifications/admin/.dm/meta.folder.json +9 -0
  344. dmart/sample/spaces/management/notifications/system/.dm/meta.folder.json +9 -0
  345. dmart/sample/spaces/management/notifications.json +1 -0
  346. dmart/sample/spaces/management/permissions/.dm/access_applications/meta.permission.json +31 -0
  347. dmart/sample/spaces/management/permissions/.dm/access_applications_world/meta.permission.json +31 -0
  348. dmart/sample/spaces/management/permissions/.dm/access_messages/meta.permission.json +23 -0
  349. dmart/sample/spaces/management/permissions/.dm/access_personal/meta.permission.json +40 -0
  350. dmart/sample/spaces/management/permissions/.dm/access_protected/meta.permission.json +33 -0
  351. dmart/sample/spaces/management/permissions/.dm/access_public/meta.permission.json +24 -0
  352. dmart/sample/spaces/management/permissions/.dm/browse_all_folders/meta.permission.json +23 -0
  353. dmart/sample/spaces/management/permissions/.dm/create_log/meta.permission.json +24 -0
  354. dmart/sample/spaces/management/permissions/.dm/interviewer/meta.permission.json +1 -0
  355. dmart/sample/spaces/management/permissions/.dm/manage_applications/meta.permission.json +1 -0
  356. dmart/sample/spaces/management/permissions/.dm/manage_debug/meta.permission.json +25 -0
  357. dmart/sample/spaces/management/permissions/.dm/manage_spaces/meta.permission.json +24 -0
  358. dmart/sample/spaces/management/permissions/.dm/meta.folder.json +1 -0
  359. dmart/sample/spaces/management/permissions/.dm/rules_management_default/meta.permission.json +32 -0
  360. dmart/sample/spaces/management/permissions/.dm/super_manager/meta.permission.json +52 -0
  361. dmart/sample/spaces/management/permissions/.dm/view_activity_log/meta.permission.json +26 -0
  362. dmart/sample/spaces/management/permissions/.dm/view_collections/meta.permission.json +29 -0
  363. dmart/sample/spaces/management/permissions/.dm/view_logs/meta.permission.json +30 -0
  364. dmart/sample/spaces/management/permissions/.dm/view_roles/meta.permission.json +29 -0
  365. dmart/sample/spaces/management/permissions/.dm/view_users/meta.permission.json +25 -0
  366. dmart/sample/spaces/management/permissions/.dm/view_world/meta.permission.json +31 -0
  367. dmart/sample/spaces/management/permissions/.dm/world/meta.permission.json +35 -0
  368. dmart/sample/spaces/management/permissions.json +1 -0
  369. dmart/sample/spaces/management/requests.json +1 -0
  370. dmart/sample/spaces/management/roles/.dm/dummy/meta.role.json +12 -0
  371. dmart/sample/spaces/management/roles/.dm/logged_in/meta.role.json +18 -0
  372. dmart/sample/spaces/management/roles/.dm/manager/meta.role.json +13 -0
  373. dmart/sample/spaces/management/roles/.dm/meta.folder.json +1 -0
  374. dmart/sample/spaces/management/roles/.dm/moderator/meta.role.json +13 -0
  375. dmart/sample/spaces/management/roles/.dm/super_admin/meta.role.json +14 -0
  376. dmart/sample/spaces/management/roles/.dm/test_role/meta.role.json +13 -0
  377. dmart/sample/spaces/management/roles/.dm/world/meta.role.json +15 -0
  378. dmart/sample/spaces/management/roles.json +1 -0
  379. dmart/sample/spaces/management/schema/.dm/admin_notification_request/attachments.media/meta.ui_schema.json +10 -0
  380. dmart/sample/spaces/management/schema/.dm/admin_notification_request/attachments.media/ui_schema.json +32 -0
  381. dmart/sample/spaces/management/schema/.dm/admin_notification_request/meta.schema.json +1 -0
  382. dmart/sample/spaces/management/schema/.dm/api/meta.schema.json +1 -0
  383. dmart/sample/spaces/management/schema/.dm/folder_rendering/meta.schema.json +1 -0
  384. dmart/sample/spaces/management/schema/.dm/health_check/meta.schema.json +17 -0
  385. dmart/sample/spaces/management/schema/.dm/meta.folder.json +1 -0
  386. dmart/sample/spaces/management/schema/.dm/meta_schema/meta.schema.json +1 -0
  387. dmart/sample/spaces/management/schema/.dm/metafile/meta.schema.json +14 -0
  388. dmart/sample/spaces/management/schema/.dm/notification/meta.schema.json +1 -0
  389. dmart/sample/spaces/management/schema/.dm/system_notification_request/attachments.media/meta.ui_schema.json +10 -0
  390. dmart/sample/spaces/management/schema/.dm/system_notification_request/attachments.media/ui_schema.json +32 -0
  391. dmart/sample/spaces/management/schema/.dm/system_notification_request/meta.schema.json +1 -0
  392. dmart/sample/spaces/management/schema/.dm/view/meta.schema.json +1 -0
  393. dmart/sample/spaces/management/schema/.dm/workflow/meta.schema.json +1 -0
  394. dmart/sample/spaces/management/schema/admin_notification_request.json +89 -0
  395. dmart/sample/spaces/management/schema/api.json +1 -0
  396. dmart/sample/spaces/management/schema/folder_rendering.json +238 -0
  397. dmart/sample/spaces/management/schema/health_check.json +8 -0
  398. dmart/sample/spaces/management/schema/meta_schema.json +74 -0
  399. dmart/sample/spaces/management/schema/metafile.json +153 -0
  400. dmart/sample/spaces/management/schema/notification.json +28 -0
  401. dmart/sample/spaces/management/schema/system_notification_request.json +57 -0
  402. dmart/sample/spaces/management/schema/view.json +23 -0
  403. dmart/sample/spaces/management/schema/workflow.json +87 -0
  404. dmart/sample/spaces/management/schema.json +1 -0
  405. dmart/sample/spaces/management/users/.dm/alibaba/meta.user.json +23 -0
  406. dmart/sample/spaces/management/users/.dm/anonymous/meta.user.json +18 -0
  407. dmart/sample/spaces/management/users/.dm/dmart/meta.user.json +26 -0
  408. dmart/sample/spaces/management/users/.dm/meta.folder.json +14 -0
  409. dmart/sample/spaces/management/workflows/.dm/channel/meta.content.json +1 -0
  410. dmart/sample/spaces/management/workflows/.dm/meta.folder.json +1 -0
  411. dmart/sample/spaces/management/workflows/channel.json +148 -0
  412. dmart/sample/spaces/management/workflows.json +1 -0
  413. dmart/sample/spaces/maqola/.dm/meta.space.json +33 -0
  414. dmart/sample/spaces/personal/.dm/meta.space.json +24 -0
  415. dmart/sample/spaces/personal/people/.dm/meta.folder.json +1 -0
  416. dmart/sample/spaces/personal/people/dmart/.dm/meta.folder.json +1 -0
  417. dmart/sample/spaces/personal/people/dmart/messages/.dm/0b5f7e7f/meta.content.json +1 -0
  418. dmart/sample/spaces/personal/people/dmart/messages/.dm/meta.folder.json +1 -0
  419. dmart/sample/spaces/personal/people/dmart/messages/.dm/mytest/meta.content.json +1 -0
  420. dmart/sample/spaces/personal/people/dmart/messages/0b5f7e7f.json +1 -0
  421. dmart/sample/spaces/personal/people/dmart/messages/mytest.json +1 -0
  422. dmart/sample/spaces/personal/people/dmart/notifications/.dm/meta.folder.json +1 -0
  423. dmart/sample/spaces/personal/people/dmart/private/.dm/inner/meta.content.json +1 -0
  424. dmart/sample/spaces/personal/people/dmart/private/.dm/meta.folder.json +1 -0
  425. dmart/sample/spaces/personal/people/dmart/private/inner.json +1 -0
  426. dmart/sample/spaces/personal/people/dmart/protected/.dm/avatar/meta.content.json +1 -0
  427. dmart/sample/spaces/personal/people/dmart/protected/.dm/meta.folder.json +1 -0
  428. dmart/sample/spaces/personal/people/dmart/protected/avatar.png +0 -0
  429. dmart/sample/spaces/personal/people/dmart/public/.dm/meta.folder.json +1 -0
  430. dmart/sample/test/.gitignore +2 -0
  431. dmart/sample/test/createcontent.json +9 -0
  432. dmart/sample/test/createmedia.json +9 -0
  433. dmart/sample/test/createmedia_entry.json +6 -0
  434. dmart/sample/test/createschema.json +8 -0
  435. dmart/sample/test/createschemawork.json +11 -0
  436. dmart/sample/test/createticket.json +13 -0
  437. dmart/sample/test/data.json +4 -0
  438. dmart/sample/test/deletecontent.json +12 -0
  439. dmart/sample/test/logo.jpeg +0 -0
  440. dmart/sample/test/my.jpg +0 -0
  441. dmart/sample/test/myticket.json +23 -0
  442. dmart/sample/test/resources.csv +12 -0
  443. dmart/sample/test/schema.json +16 -0
  444. dmart/sample/test/temp.json +1 -0
  445. dmart/sample/test/test.dmart +45 -0
  446. dmart/sample/test/ticket_schema.json +23 -0
  447. dmart/sample/test/ticket_workflow.json +85 -0
  448. dmart/sample/test/ticketbody.json +4 -0
  449. dmart/sample/test/ticketcontent.json +14 -0
  450. dmart/sample/test/updatecontent.json +20 -0
  451. dmart/sample/test/workflow_schema.json +68 -0
  452. dmart/scheduled_notification_handler.py +121 -0
  453. dmart/schema_migration.py +208 -0
  454. dmart/schema_modulate.py +192 -0
  455. dmart/set_admin_passwd.py +75 -0
  456. dmart/sync.py +202 -0
  457. dmart/test_utils.py +34 -0
  458. dmart/utils/__init__.py +0 -0
  459. dmart/utils/access_control.py +306 -0
  460. dmart/utils/async_request.py +8 -0
  461. dmart/utils/exporter.py +309 -0
  462. dmart/utils/firebase_notifier.py +57 -0
  463. dmart/utils/generate_email.py +37 -0
  464. dmart/utils/helpers.py +352 -0
  465. dmart/utils/hypercorn_config.py +12 -0
  466. dmart/utils/internal_error_code.py +60 -0
  467. dmart/utils/jwt.py +124 -0
  468. dmart/utils/logger.py +167 -0
  469. dmart/utils/middleware.py +99 -0
  470. dmart/utils/notification.py +75 -0
  471. dmart/utils/password_hashing.py +16 -0
  472. dmart/utils/plugin_manager.py +202 -0
  473. dmart/utils/query_policies_helper.py +128 -0
  474. dmart/utils/regex.py +44 -0
  475. dmart/utils/repository.py +529 -0
  476. dmart/utils/router_helper.py +19 -0
  477. dmart/utils/settings.py +212 -0
  478. dmart/utils/sms_notifier.py +21 -0
  479. dmart/utils/social_sso.py +67 -0
  480. dmart/utils/templates/activation.html.j2 +26 -0
  481. dmart/utils/templates/reminder.html.j2 +17 -0
  482. dmart/utils/ticket_sys_utils.py +203 -0
  483. dmart/utils/web_notifier.py +29 -0
  484. dmart/websocket.py +231 -0
  485. dmart-1.4.40.post8.dist-info/METADATA +75 -0
  486. dmart-1.4.40.post8.dist-info/RECORD +489 -0
  487. dmart-1.4.40.post8.dist-info/WHEEL +5 -0
  488. dmart-1.4.40.post8.dist-info/entry_points.txt +2 -0
  489. dmart-1.4.40.post8.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1013 @@
1
+ import json
2
+ import os
3
+ import subprocess
4
+ import sys
5
+ from datetime import datetime
6
+ from pathlib import Path
7
+ from fastapi import status
8
+ from fastapi.encoders import jsonable_encoder
9
+ import aiofiles
10
+ from data_adapters.file.redis_services import RedisServices
11
+ from models import core, api
12
+ from utils import regex
13
+
14
+ from utils.helpers import camel_case, alter_dict_keys, str_to_datetime, flatten_all
15
+ from utils.internal_error_code import InternalErrorCode
16
+ from utils.query_policies_helper import get_user_query_policies
17
+ from utils.settings import settings
18
+
19
+
20
+ async def redis_query_search(
21
+ query: api.Query, user_shortname: str, redis_query_policies: list = []
22
+ ) -> tuple:
23
+ search_res: list = []
24
+ total = 0
25
+
26
+ if not query.filter_schema_names:
27
+ query.filter_schema_names = ["meta"]
28
+
29
+ created_at_search = ""
30
+
31
+ if query.from_date and query.to_date:
32
+ created_at_search = (
33
+ "[" + f"{query.from_date.timestamp()} {query.to_date.timestamp()}" + "]"
34
+ )
35
+
36
+ elif query.from_date:
37
+ created_at_search = (
38
+ "["
39
+ + f"{query.from_date.timestamp()} {datetime(2199, 12, 31).timestamp()}"
40
+ + "]"
41
+ )
42
+
43
+ elif query.to_date:
44
+ created_at_search = (
45
+ "["
46
+ + f"{datetime(2010, 1, 1).timestamp()} {query.to_date.timestamp()}"
47
+ + "]"
48
+ )
49
+
50
+ limit = query.limit
51
+ offset = query.offset
52
+ if len(query.filter_schema_names) > 1 and query.sort_by:
53
+ limit += offset
54
+ offset = 0
55
+
56
+ async with RedisServices() as redis_services:
57
+ for schema_name in query.filter_schema_names:
58
+ redis_res = await redis_services.search(
59
+ space_name=query.space_name,
60
+ schema_name=schema_name,
61
+ search=str(query.search),
62
+ filters={
63
+ "resource_type": query.filter_types or [],
64
+ "shortname": query.filter_shortnames or [],
65
+ "tags": query.filter_tags or [],
66
+ "subpath": [query.subpath],
67
+ "query_policies": redis_query_policies,
68
+ "user_shortname": user_shortname,
69
+ "created_at": created_at_search,
70
+ },
71
+ exact_subpath=query.exact_subpath,
72
+ limit=limit,
73
+ offset=offset,
74
+ highlight_fields=list(query.highlight_fields.keys()),
75
+ sort_by=query.sort_by,
76
+ sort_type=query.sort_type or api.SortType.ascending,
77
+ )
78
+
79
+ if redis_res:
80
+ search_res.extend(redis_res["data"])
81
+ total += redis_res["total"]
82
+ return search_res, total
83
+
84
+
85
+ async def get_record_from_redis_doc(
86
+ db,
87
+ space_name: str,
88
+ doc: dict,
89
+ retrieve_json_payload: bool = False,
90
+ retrieve_attachments: bool = False,
91
+ validate_schema: bool = False,
92
+ filter_types: list | None = None,
93
+ retrieve_lock_status: bool = False,
94
+ ) -> core.Record:
95
+ meta_doc_content = {}
96
+ payload_doc_content = {}
97
+ resource_class = getattr(
98
+ sys.modules["models.core"],
99
+ camel_case(doc["resource_type"]),
100
+ )
101
+
102
+ for key, value in doc.items():
103
+ if key in resource_class.model_fields.keys():
104
+ meta_doc_content[key] = value
105
+ elif key not in RedisServices.SYS_ATTRIBUTES:
106
+ payload_doc_content[key] = value
107
+
108
+ async with RedisServices() as redis_services:
109
+ # Get payload doc
110
+ if (
111
+ not payload_doc_content
112
+ and retrieve_json_payload
113
+ and "payload_doc_id" in doc
114
+ ):
115
+ payload_doc_content = await redis_services.get_payload_doc(
116
+ doc["payload_doc_id"], doc["resource_type"]
117
+ )
118
+
119
+ meta_doc_content["created_at"] = datetime.fromtimestamp(
120
+ meta_doc_content["created_at"]
121
+ )
122
+ meta_doc_content["updated_at"] = datetime.fromtimestamp(
123
+ meta_doc_content["updated_at"]
124
+ )
125
+ resource_obj = resource_class.model_validate(meta_doc_content)
126
+ resource_base_record = resource_obj.to_record(
127
+ doc["subpath"],
128
+ meta_doc_content["shortname"],
129
+ [],
130
+ )
131
+
132
+ # Get lock data
133
+ if retrieve_lock_status:
134
+ locked_data = await redis_services.get_lock_doc(
135
+ space_name,
136
+ doc["subpath"],
137
+ doc["shortname"],
138
+ )
139
+ if locked_data:
140
+ resource_base_record.attributes["locked"] = locked_data
141
+
142
+ # Get attachments
143
+ entry_path = (
144
+ settings.spaces_folder
145
+ / f"{space_name}/{doc['subpath']}/.dm/{meta_doc_content['shortname']}"
146
+ )
147
+ if retrieve_attachments and entry_path.is_dir():
148
+ resource_base_record.attachments = await db.get_entry_attachments(
149
+ subpath=f"{doc['subpath']}/{meta_doc_content['shortname']}",
150
+ attachments_path=entry_path,
151
+ filter_types=filter_types,
152
+ retrieve_json_payload=retrieve_json_payload,
153
+ )
154
+
155
+ if (
156
+ retrieve_json_payload
157
+ and resource_base_record.attributes["payload"]
158
+ and resource_base_record.attributes["payload"].content_type == core.ContentType.json
159
+ ):
160
+ resource_base_record.attributes["payload"].body = payload_doc_content
161
+
162
+ # Validate payload
163
+ if (
164
+ retrieve_json_payload
165
+ and resource_obj.payload
166
+ and resource_obj.payload.schema_shortname
167
+ and payload_doc_content is not None
168
+ and validate_schema
169
+ ):
170
+ await db.validate_payload_with_schema(
171
+ payload_data=payload_doc_content,
172
+ space_name=space_name,
173
+ schema_shortname=resource_obj.payload.schema_shortname,
174
+ )
175
+
176
+ if isinstance(resource_base_record, core.Record):
177
+ return resource_base_record
178
+ else:
179
+ return core.Record()
180
+
181
+ async def redis_query_aggregate(
182
+ query: api.Query,
183
+ redis_query_policies: list = [],
184
+ ) -> list:
185
+ if not query.aggregation_data:
186
+ return []
187
+
188
+ if len(query.filter_schema_names) > 1:
189
+ raise api.Exception(
190
+ status.HTTP_400_BAD_REQUEST,
191
+ error=api.Error(
192
+ type="query", code=InternalErrorCode.INVALID_STANDALONE_DATA,
193
+ message="only one argument is allowed in filter_schema_names"
194
+ ),
195
+ )
196
+
197
+ created_at_search = ""
198
+ if query.from_date and query.to_date:
199
+ created_at_search = (
200
+ "[" + f"{query.from_date.timestamp()} {query.to_date.timestamp()}" + "]"
201
+ )
202
+
203
+ elif query.from_date:
204
+ created_at_search = (
205
+ "["
206
+ + f"{query.from_date.timestamp()} {datetime(2199, 12, 31).timestamp()}"
207
+ + "]"
208
+ )
209
+
210
+ elif query.to_date:
211
+ created_at_search = (
212
+ "["
213
+ + f"{datetime(2010, 1, 1).timestamp()} {query.to_date.timestamp()}"
214
+ + "]"
215
+ )
216
+
217
+ async with RedisServices() as redis_services:
218
+ value = await redis_services.aggregate(
219
+ space_name=query.space_name,
220
+ schema_name=query.filter_schema_names[0],
221
+ search=str(query.search),
222
+ filters={
223
+ "resource_type": query.filter_types or [],
224
+ "shortname": query.filter_shortnames or [],
225
+ "subpath": [query.subpath],
226
+ "query_policies": redis_query_policies,
227
+ "created_at": created_at_search,
228
+ },
229
+ load=query.aggregation_data.load,
230
+ group_by=query.aggregation_data.group_by,
231
+ reducers=query.aggregation_data.reducers,
232
+ exact_subpath=query.exact_subpath,
233
+ sort_by=query.sort_by,
234
+ max=query.limit,
235
+ sort_type=query.sort_type or api.SortType.ascending,
236
+ )
237
+ if isinstance(value, list):
238
+ return value
239
+ else:
240
+ return []
241
+
242
+
243
+ async def serve_query_space(db, query, logged_in_user):
244
+ records = []
245
+ total = 0
246
+
247
+ spaces = await db.get_spaces()
248
+ for space_json in spaces.values():
249
+ space = core.Space.model_validate_json(space_json)
250
+ from utils.access_control import access_control
251
+ if await access_control.check_space_access(
252
+ logged_in_user, space.shortname
253
+ ):
254
+ total += 1
255
+ records.append(
256
+ space.to_record(
257
+ query.subpath,
258
+ space.shortname,
259
+ query.include_fields if query.include_fields else [],
260
+ )
261
+ )
262
+ if not query.sort_by:
263
+ query.sort_by = "ordinal"
264
+ if records:
265
+ record_fields = list(records[0].model_fields.keys())
266
+ records = sorted(
267
+ records,
268
+ key=lambda d: d.__getattribute__(query.sort_by)
269
+ if query.sort_by in record_fields
270
+ else d.attributes[query.sort_by]
271
+ if query.sort_by in d.attributes and d.attributes[query.sort_by] is not None
272
+ else 1,
273
+ reverse=(query.sort_type == api.SortType.descending),
274
+ )
275
+
276
+ return total, records
277
+
278
+
279
+ async def serve_query_search(db, query, logged_in_user):
280
+ records = []
281
+ total = 0
282
+
283
+ redis_query_policies = await get_user_query_policies(
284
+ db, logged_in_user, query.space_name, query.subpath
285
+ )
286
+
287
+ search_res, total = await redis_query_search(query, logged_in_user, redis_query_policies)
288
+ res_data = []
289
+ for redis_document in search_res:
290
+ res_data.append(json.loads(redis_document))
291
+ if len(query.filter_schema_names) > 1:
292
+ if query.sort_by:
293
+ res_data = sorted(
294
+ res_data,
295
+ key=lambda d: d[query.sort_by]
296
+ if query.sort_by in d
297
+ else d.get("payload", {})[query.sort_by]
298
+ if query.sort_by in d.get("payload", {})
299
+ else "",
300
+ reverse=(query.sort_type == api.SortType.descending),
301
+ )
302
+ res_data = res_data[query.offset: (query.limit + query.offset)]
303
+
304
+ for redis_doc_dict in res_data:
305
+ try:
306
+ resource_base_record = await get_record_from_redis_doc(
307
+ db,
308
+ space_name=query.space_name,
309
+ doc=redis_doc_dict,
310
+ retrieve_json_payload=query.retrieve_json_payload,
311
+ retrieve_attachments=query.retrieve_attachments,
312
+ validate_schema=query.validate_schema,
313
+ filter_types=query.filter_types,
314
+ retrieve_lock_status=query.retrieve_lock_status,
315
+ )
316
+ except Exception as e:
317
+ print("Error in get_record_from_redis_doc", e)
318
+ continue
319
+
320
+ # Don't repeat the same entry comming from different indices
321
+ # if resource_base_record in records:
322
+ # total -= 1
323
+ # continue
324
+
325
+ if query.highlight_fields:
326
+ for key, value in query.highlight_fields.items():
327
+ resource_base_record.attributes[value] = getattr(
328
+ redis_doc_dict, key, None
329
+ )
330
+
331
+ resource_base_record.attributes = alter_dict_keys(
332
+ jsonable_encoder(resource_base_record.attributes, exclude_none=True),
333
+ query.include_fields,
334
+ query.exclude_fields,
335
+ )
336
+
337
+ records.append(resource_base_record)
338
+
339
+ return total, records
340
+
341
+
342
+ async def serve_query_subpath_sorting(query, records):
343
+ sort_reverse = (
344
+ query.sort_type is not None
345
+ and query.sort_type == api.SortType.descending
346
+ )
347
+ if query.sort_by in core.Record.model_fields:
348
+ records = sorted(
349
+ records,
350
+ key=lambda record: record.__getattribute__(
351
+ str(query.sort_by)),
352
+ reverse=sort_reverse,
353
+ )
354
+ else:
355
+ records = sorted(
356
+ records,
357
+ key=lambda record: record.attributes[str(
358
+ query.sort_by)],
359
+ reverse=sort_reverse,
360
+ )
361
+
362
+ return records
363
+
364
+
365
+ async def serve_query_subpath_check_payload(db, resource_base_record, path, resource_obj, query):
366
+ payload_body = resource_base_record.attributes[
367
+ "payload"
368
+ ].body
369
+ if not payload_body or isinstance(payload_body, str):
370
+ async with aiofiles.open(
371
+ path / resource_obj.payload.body, "r"
372
+ ) as payload_file_content:
373
+ payload_body = json.loads(
374
+ await payload_file_content.read()
375
+ )
376
+
377
+ if query.validate_schema:
378
+ await db.validate_payload_with_schema(
379
+ payload_data=payload_body,
380
+ space_name=query.space_name,
381
+ schema_shortname=resource_obj.payload.schema_shortname,
382
+ )
383
+
384
+
385
+ async def set_attachment_for_payload(db, path, folder_obj, folder_record, query, meta_path, shortname):
386
+ if (
387
+ query.retrieve_json_payload
388
+ and folder_obj.payload
389
+ and folder_obj.payload.content_type
390
+ and folder_obj.payload.content_type == core.ContentType.json
391
+ and isinstance(folder_obj.payload.body, str)
392
+ and (path / folder_obj.payload.body).is_file()
393
+ ):
394
+ async with aiofiles.open(
395
+ path / folder_obj.payload.body, "r"
396
+ ) as payload_file_content:
397
+ folder_record.attributes["payload"].body = json.loads(
398
+ await payload_file_content.read()
399
+ )
400
+ if os.path.exists(meta_path / shortname):
401
+ folder_record.attachments = await db.get_entry_attachments(
402
+ subpath=f"{query.subpath if query.subpath != '/' else ''}/{shortname}",
403
+ attachments_path=(meta_path / shortname),
404
+ filter_types=query.filter_types,
405
+ include_fields=query.include_fields,
406
+ retrieve_json_payload=query.retrieve_json_payload,
407
+ )
408
+
409
+ return folder_record
410
+
411
+
412
+ async def serve_query_subpath(db, query, logged_in_user):
413
+ records : list[core.Record] = []
414
+ total = 0
415
+
416
+ from utils.access_control import access_control
417
+
418
+ subpath = query.subpath
419
+ if subpath[0] == "/":
420
+ subpath = "." + subpath
421
+
422
+ path = (
423
+ settings.spaces_folder
424
+ / query.space_name
425
+ / subpath
426
+ )
427
+
428
+ if query.include_fields is None:
429
+ query.include_fields = []
430
+
431
+ meta_path = path / ".dm"
432
+ if meta_path.is_dir():
433
+ path_iterator = os.scandir(meta_path)
434
+ async with RedisServices() as redis_services:
435
+ for entry in path_iterator:
436
+ if not entry.is_dir():
437
+ continue
438
+ subpath_iterator = os.scandir(str(entry.path))
439
+ for one in subpath_iterator:
440
+ # for one in path.glob(entries_glob):
441
+ match = regex.FILE_PATTERN.search(str(one.path))
442
+ if not match or not one.is_file():
443
+ continue
444
+
445
+ shortname = match.group(1)
446
+ resource_name = match.group(2).lower()
447
+ if (
448
+ query.filter_types
449
+ and core.ResourceType(resource_name) not in query.filter_types
450
+ ):
451
+ continue
452
+
453
+ if (
454
+ query.filter_shortnames
455
+ and shortname not in query.filter_shortnames
456
+ ):
457
+ continue
458
+
459
+ resource_class = getattr(
460
+ sys.modules["models.core"], camel_case(
461
+ resource_name)
462
+ )
463
+ async with aiofiles.open(str(one.path), "r") as meta_file:
464
+ resource_obj = resource_class.model_validate_json(
465
+ await meta_file.read()
466
+ )
467
+
468
+ if query.filter_tags and (
469
+ not resource_obj.tags
470
+ or not any(
471
+ item in resource_obj.tags
472
+ for item in query.filter_tags
473
+ )
474
+ ):
475
+ continue
476
+
477
+ # apply check access
478
+ if not await access_control.check_access(
479
+ user_shortname=logged_in_user,
480
+ space_name=query.space_name,
481
+ subpath=query.subpath,
482
+ resource_type=core.ResourceType(resource_name),
483
+ action_type=core.ActionType.view,
484
+ resource_is_active=resource_obj.is_active,
485
+ resource_owner_shortname=resource_obj.owner_shortname,
486
+ resource_owner_group=resource_obj.owner_group_shortname,
487
+ entry_shortname=shortname,
488
+ ):
489
+ continue
490
+
491
+ total += 1
492
+ if len(records) >= query.limit or total < query.offset:
493
+ continue
494
+
495
+ resource_base_record : core.Record = resource_obj.to_record(
496
+ query.subpath,
497
+ shortname,
498
+ query.include_fields,
499
+ )
500
+ if query.retrieve_lock_status and resource_base_record:
501
+ locked_data = await redis_services.get_lock_doc(
502
+ query.space_name,
503
+ query.subpath,
504
+ resource_obj.shortname,
505
+ )
506
+ if locked_data:
507
+ resource_base_record.attributes[
508
+ "locked"
509
+ ] = locked_data
510
+
511
+ if (
512
+ query.retrieve_json_payload
513
+ and resource_obj.payload
514
+ and resource_obj.payload.content_type
515
+ and resource_obj.payload.content_type
516
+ == core.ContentType.json
517
+ and (path / resource_obj.payload.body).is_file()
518
+ ):
519
+ async with aiofiles.open(
520
+ path / resource_obj.payload.body, "r"
521
+ ) as payload_file_content:
522
+ resource_base_record.attributes[
523
+ "payload"
524
+ ].body = json.loads(
525
+ await payload_file_content.read()
526
+ )
527
+
528
+ if (
529
+ resource_obj.payload
530
+ and resource_obj.payload.schema_shortname
531
+ ):
532
+ try:
533
+ await serve_query_subpath_check_payload(db, resource_base_record, path, resource_obj, query)
534
+ except Exception:
535
+ continue
536
+
537
+ resource_base_record.attachments = (
538
+ await db.get_entry_attachments(
539
+ subpath=f"{query.subpath}/{shortname}",
540
+ attachments_path=(meta_path / shortname),
541
+ filter_types=query.filter_types,
542
+ include_fields=query.include_fields,
543
+ retrieve_json_payload=query.retrieve_json_payload,
544
+ )
545
+ )
546
+ records.append(resource_base_record)
547
+
548
+ subpath_iterator.close()
549
+ if path_iterator:
550
+ path_iterator.close()
551
+
552
+ # Get all matching sub folders
553
+ # apply check access
554
+
555
+ if meta_path.is_dir():
556
+ subfolders_iterator = os.scandir(path)
557
+ for one in subfolders_iterator:
558
+ if not one.is_dir():
559
+ continue
560
+
561
+ subfolder_meta = Path(one.path + "/.dm/meta.folder.json")
562
+
563
+ match = regex.FOLDER_PATTERN.search(str(subfolder_meta))
564
+
565
+ if not match or not subfolder_meta.is_file():
566
+ continue
567
+
568
+ shortname = match.group(1)
569
+ if not await access_control.check_access(
570
+ user_shortname=logged_in_user,
571
+ space_name=query.space_name,
572
+ subpath=f"{query.subpath}/{shortname}",
573
+ resource_type=core.ResourceType.folder,
574
+ action_type=core.ActionType.query,
575
+ entry_shortname=shortname
576
+ ):
577
+ continue
578
+
579
+ if (
580
+ query.filter_shortnames
581
+ and shortname not in query.filter_shortnames
582
+ ):
583
+ continue
584
+
585
+ total += 1
586
+ if len(records) >= query.limit or total < query.offset:
587
+ continue
588
+
589
+ folder_obj = core.Folder.model_validate_json(
590
+ subfolder_meta.read_text()
591
+ )
592
+ folder_record = folder_obj.to_record(
593
+ query.subpath,
594
+ shortname,
595
+ query.include_fields,
596
+ )
597
+
598
+ await set_attachment_for_payload(
599
+ db, path, folder_obj, folder_record, query, meta_path, shortname
600
+ )
601
+
602
+ records.append(folder_record)
603
+
604
+ if subfolders_iterator:
605
+ subfolders_iterator.close()
606
+
607
+ if query.sort_by:
608
+ records = await serve_query_subpath_sorting(query, records)
609
+
610
+ return total, records
611
+
612
+
613
+ async def serve_query_counters(query, logged_in_user):
614
+ records : list = []
615
+ total = 0
616
+ from utils.access_control import access_control
617
+ if not await access_control.check_access(
618
+ user_shortname=logged_in_user,
619
+ space_name=query.space_name,
620
+ subpath=query.subpath,
621
+ resource_type=core.ResourceType.content,
622
+ action_type=core.ActionType.query
623
+ ):
624
+ raise api.Exception(
625
+ status.HTTP_401_UNAUTHORIZED,
626
+ api.Error(
627
+ type="request",
628
+ code=InternalErrorCode.NOT_ALLOWED,
629
+ message="You don't have permission to this action [16]",
630
+ ),
631
+ )
632
+ async with RedisServices() as redis_services:
633
+ for schema_name in query.filter_schema_names:
634
+ redis_res = await redis_services.get_count(
635
+ space_name=query.space_name,
636
+ schema_shortname=schema_name,
637
+ )
638
+ total += int(redis_res)
639
+
640
+ return total, records
641
+
642
+
643
+ async def serve_query_tags(db, query, user_shortname):
644
+ records = []
645
+ total = 0
646
+
647
+ redis_query_policies = await get_user_query_policies(
648
+ db, user_shortname, query.space_name, query.subpath
649
+ )
650
+
651
+ query.sort_by = "tags"
652
+ query.aggregation_data = api.RedisAggregate(
653
+ group_by=["@tags"],
654
+ reducers=[
655
+ api.RedisReducer(
656
+ reducer_name="count",
657
+ alias="freq"
658
+ )
659
+ ]
660
+ )
661
+ rows = await redis_query_aggregate(
662
+ query=query,
663
+ redis_query_policies=redis_query_policies
664
+ )
665
+ records.append(
666
+ core.Record(
667
+ resource_type=core.ResourceType.content,
668
+ shortname="tags_frequency",
669
+ subpath=query.subpath,
670
+ attributes={"result": rows},
671
+ )
672
+ )
673
+ total = 1
674
+
675
+ return total, records
676
+
677
+
678
+ async def serve_query_random(db, query, user_shortname):
679
+ records = []
680
+ total = 0
681
+
682
+ redis_query_policies = await get_user_query_policies(
683
+ db, user_shortname, query.space_name, query.subpath
684
+ )
685
+ query.aggregation_data = api.RedisAggregate(
686
+ load=["@__key"],
687
+ group_by=["@resource_type"],
688
+ reducers=[
689
+ api.RedisReducer(
690
+ reducer_name="random_sample",
691
+ alias="id",
692
+ args=["@__key", query.limit]
693
+ )
694
+ ]
695
+ )
696
+ async with RedisServices() as redis_services:
697
+ rows = await redis_query_aggregate(
698
+ query=query,
699
+ redis_query_policies=redis_query_policies
700
+ )
701
+ ids = []
702
+ for row in rows:
703
+ ids.extend(row[3])
704
+ docs = await redis_services.get_docs_by_ids(ids)
705
+ total = len(ids)
706
+ for doc in docs:
707
+ doc = doc[0]
708
+ if query.retrieve_json_payload and doc.get("payload_doc_id", None):
709
+ doc["payload"]["body"] = await redis_services.get_payload_doc(
710
+ doc["payload_doc_id"], doc["resource_type"]
711
+ )
712
+ record = core.Record(
713
+ shortname=doc["shortname"],
714
+ resource_type=doc["resource_type"],
715
+ uuid=doc["uuid"],
716
+ subpath=doc["subpath"],
717
+ attributes={"payload": doc.get("payload")},
718
+ )
719
+ entry_path = (
720
+ settings.spaces_folder
721
+ / f"{query.space_name}/{doc['subpath']}/.dm/{doc['shortname']}"
722
+ )
723
+ if query.retrieve_attachments and entry_path.is_dir():
724
+ record.attachments = await db.get_entry_attachments(
725
+ subpath=f"{doc['subpath']}/{doc['shortname']}",
726
+ attachments_path=entry_path,
727
+ filter_types=query.filter_types,
728
+ include_fields=query.include_fields,
729
+ retrieve_json_payload=query.retrieve_json_payload,
730
+ )
731
+ records.append(record)
732
+
733
+ return total, records
734
+
735
+
736
+ async def serve_query_history(query, logged_in_user):
737
+ records = []
738
+ total = 0
739
+ from utils.access_control import access_control
740
+ if not await access_control.check_access(
741
+ user_shortname=logged_in_user,
742
+ space_name=query.space_name,
743
+ subpath=query.subpath,
744
+ resource_type=core.ResourceType.history,
745
+ action_type=core.ActionType.query,
746
+ ):
747
+ raise api.Exception(
748
+ status.HTTP_401_UNAUTHORIZED,
749
+ api.Error(
750
+ type="request",
751
+ code=InternalErrorCode.NOT_ALLOWED,
752
+ message="You don't have permission to this action [17]",
753
+ ),
754
+ )
755
+
756
+ if not query.filter_shortnames:
757
+ raise api.Exception(
758
+ status.HTTP_400_BAD_REQUEST,
759
+ api.Error(
760
+ type="request",
761
+ code=InternalErrorCode.MISSING_FILTER_SHORTNAMES,
762
+ message="filter_shortnames is missing",
763
+ ),
764
+ )
765
+
766
+ path = Path(f"{settings.spaces_folder}/{query.space_name}/"
767
+ f"{query.subpath}/.dm/{query.filter_shortnames[0]}/history.jsonl")
768
+ if path.is_file():
769
+ r1 = subprocess.Popen(
770
+ ["tail", "-n", f"+{query.offset}", path], stdout=subprocess.PIPE, stderr=subprocess.PIPE,
771
+ )
772
+ rn = subprocess.Popen(
773
+ ["sed", "-e", "$a\\\n"], stdin=r1.stdout, stdout=subprocess.PIPE,
774
+ )
775
+ r2 = subprocess.Popen(
776
+ ["head", "-n", f"{query.limit}"], stdin=rn.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
777
+ )
778
+ r3 = subprocess.Popen(
779
+ ["tac"], stdin=r2.stdout, stdout=subprocess.PIPE,
780
+ stderr=subprocess.PIPE,
781
+ )
782
+ r4, _ = r3.communicate()
783
+ if r4 is None:
784
+ result = []
785
+ else:
786
+ result = list(
787
+ filter(
788
+ None,
789
+ r4.decode().split("\n"),
790
+ )
791
+ )
792
+
793
+ r, _ = subprocess.Popen(
794
+ f"wc -l {path}".split(" "), stdout=subprocess.PIPE, stderr=subprocess.PIPE
795
+ ).communicate()
796
+
797
+ if r is None:
798
+ total = 0
799
+ else:
800
+ total = int(
801
+ r.decode().split()[0],
802
+ 10,
803
+ )
804
+
805
+ for line in result:
806
+ action_obj = json.loads(line)
807
+
808
+ records.append(
809
+ core.Record(
810
+ resource_type=core.ResourceType.history,
811
+ shortname=query.filter_shortnames[0],
812
+ subpath=query.subpath,
813
+ attributes=action_obj,
814
+ ),
815
+ )
816
+
817
+ return total, records
818
+
819
+
820
+ async def serve_query_events(query, logged_in_user):
821
+ records = []
822
+ total = 0
823
+
824
+ trimmed_subpath = query.subpath
825
+ if trimmed_subpath[0] == "/":
826
+ trimmed_subpath = trimmed_subpath[1:]
827
+
828
+ path = Path(
829
+ f"{settings.spaces_folder}/{query.space_name}/.dm/events.jsonl")
830
+ if path.is_file():
831
+ result = []
832
+ if query.search:
833
+ p = subprocess.Popen(
834
+ ["grep", f'"{query.search}"', path], stdout=subprocess.PIPE
835
+ )
836
+ p = subprocess.Popen(
837
+ ["tail", "-n", f"{query.limit + query.offset}"],
838
+ stdin=p.stdout,
839
+ stdout=subprocess.PIPE,
840
+ )
841
+ p = subprocess.Popen(
842
+ ["tac"], stdin=p.stdout, stdout=subprocess.PIPE
843
+ )
844
+ if query.offset > 0:
845
+ p = subprocess.Popen(
846
+ ["sed", f"1,{query.offset}d"],
847
+ stdin=p.stdout,
848
+ stdout=subprocess.PIPE,
849
+ )
850
+ r, _ = p.communicate()
851
+ result = list(filter(None, r.decode("utf-8").split("\n")))
852
+ else:
853
+ r1 = subprocess.Popen(
854
+ ["tail", "-n", f"{query.limit + query.offset}", path], stdout=subprocess.PIPE,
855
+ )
856
+
857
+ r1 = subprocess.Popen(
858
+ ["sed", "-e", "$a\\\n"], stdin=r1.stdout, stdout=subprocess.PIPE,
859
+ )
860
+ if query.offset > 0:
861
+ r1 = subprocess.Popen(
862
+ ["head", "-n", f"{query.limit}"], stdin=r1.stdout, stdout=subprocess.PIPE,
863
+ )
864
+
865
+ r6, _ = subprocess.Popen(
866
+ ["tac"], stdin=r1.stdout, stdout=subprocess.PIPE,
867
+ ).communicate()
868
+
869
+ if r6 is None:
870
+ result = []
871
+ else:
872
+ result = list(
873
+ filter(
874
+ None,
875
+ r6.decode().split("\n"),
876
+ )
877
+ )
878
+
879
+ r, _ = subprocess.Popen(
880
+ f"wc -l {path}".split(" "), stdout=subprocess.PIPE, stderr=subprocess.PIPE
881
+ ).communicate()
882
+
883
+ if r is None:
884
+ total = 0
885
+ else:
886
+ total = int(
887
+ r.decode().split()[0],
888
+ 10,
889
+ )
890
+
891
+ for line in result:
892
+ action_obj = json.loads(line)
893
+
894
+ if (
895
+ query.from_date
896
+ and str_to_datetime(action_obj["timestamp"]) < query.from_date
897
+ ):
898
+ continue
899
+
900
+ if (
901
+ query.to_date
902
+ and str_to_datetime(action_obj["timestamp"]) > query.to_date
903
+ ):
904
+ break
905
+ from utils.access_control import access_control
906
+ if not await access_control.check_access(
907
+ user_shortname=logged_in_user,
908
+ space_name=query.space_name,
909
+ subpath=action_obj.get(
910
+ "resource", {}).get("subpath", "/"),
911
+ resource_type=action_obj["resource"]["type"],
912
+ action_type=core.ActionType(action_obj["request"]),
913
+ ):
914
+ continue
915
+ records.append(
916
+ core.Record(
917
+ resource_type=action_obj["resource"]["type"],
918
+ shortname=action_obj["resource"]["shortname"],
919
+ subpath=action_obj["resource"]["subpath"],
920
+ attributes=action_obj,
921
+ ),
922
+ )
923
+
924
+ return total, records
925
+
926
+
927
+ async def serve_query_aggregation(db, query, user_shortname):
928
+ records = []
929
+ total = 0
930
+
931
+ redis_query_policies = await get_user_query_policies(
932
+ db, user_shortname, query.space_name, query.subpath
933
+ )
934
+ rows = await redis_query_aggregate(
935
+ query=query, redis_query_policies=redis_query_policies
936
+ )
937
+ total = len(rows)
938
+ for idx, row in enumerate(rows):
939
+ record = core.Record(
940
+ resource_type=core.ResourceType.content,
941
+ shortname=str(idx + 1),
942
+ subpath=query.subpath,
943
+ attributes=row["extra_attributes"]
944
+ )
945
+ records.append(record)
946
+
947
+ return total, records
948
+
949
+ def parse_redis_response(rows: list) -> list:
950
+ mylist: list = []
951
+ for one in rows:
952
+ mydict = {}
953
+ key: str | None = None
954
+ for i, value in enumerate(one):
955
+ if i % 2 == 0:
956
+ key = value
957
+ elif key:
958
+ mydict[key] = value
959
+ mylist.append(mydict)
960
+ return mylist
961
+
962
+ async def generate_payload_string(
963
+ db,
964
+ space_name: str,
965
+ subpath: str,
966
+ shortname: str,
967
+ payload: dict,
968
+ ):
969
+ payload_string = ""
970
+ # Remove system related attributes from payload
971
+ for attr in RedisServices.SYS_ATTRIBUTES:
972
+ if attr in payload:
973
+ del payload[attr]
974
+
975
+ # Generate direct payload string
976
+ payload_values = set(flatten_all(payload).values())
977
+ payload_string += ",".join([str(i)
978
+ for i in payload_values if i is not None])
979
+
980
+ # Generate attachments payload string
981
+ attachments: dict[str, list] = await db.get_entry_attachments(
982
+ subpath=f"{subpath}/{shortname}",
983
+ attachments_path=(
984
+ settings.spaces_folder
985
+ / f"{space_name}/{subpath}/.dm/{shortname}"
986
+ ),
987
+ retrieve_json_payload=True,
988
+ include_fields=[
989
+ "shortname",
990
+ "displayname",
991
+ "description",
992
+ "payload",
993
+ "tags",
994
+ "owner_shortname",
995
+ "owner_group_shortname",
996
+ "body",
997
+ "state",
998
+ ],
999
+ )
1000
+ if not attachments:
1001
+ return payload_string.strip(",")
1002
+
1003
+ # Convert Record objects to dict
1004
+ dict_attachments = {}
1005
+ for k, v in attachments.items():
1006
+ dict_attachments[k] = [i.model_dump() for i in v]
1007
+
1008
+ attachments_values = set(flatten_all(dict_attachments).values())
1009
+ attachments_payload_string = ",".join(
1010
+ [str(i) for i in attachments_values if i is not None]
1011
+ )
1012
+ payload_string += attachments_payload_string
1013
+ return payload_string.strip(",")