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,1879 @@
1
+ from datetime import datetime
2
+ from io import BytesIO
3
+ from typing import Any
4
+
5
+ from fastapi import status
6
+ from models.api import Exception as API_Exception, Error as API_Error
7
+ from utils import password_hashing
8
+ from utils.generate_email import generate_email_from_template, generate_subject
9
+ from data_adapters.file.custom_validations import validate_csv_with_schema, validate_jsonl_with_schema
10
+ from utils.internal_error_code import InternalErrorCode
11
+ from utils.router_helper import is_space_exist
12
+ from utils.ticket_sys_utils import (
13
+ set_init_state_from_record,
14
+ set_init_state_for_record,
15
+ transite,
16
+ post_transite,
17
+ check_open_state,
18
+ )
19
+ import models.api as api
20
+ import models.core as core
21
+ from models.enums import (
22
+ ContentType,
23
+ RequestType,
24
+ ResourceType,
25
+ DataAssetType,
26
+ )
27
+ import sys
28
+ import json
29
+ from utils.access_control import access_control
30
+ import utils.repository as repository
31
+ from utils.helpers import (
32
+ camel_case,
33
+ flatten_dict,
34
+ )
35
+ from utils.settings import settings
36
+ from utils.plugin_manager import plugin_manager
37
+ from api.user.service import (
38
+ send_email,
39
+ send_sms,
40
+ )
41
+ from languages.loader import languages
42
+ from data_adapters.adapter import data_adapter as db
43
+ from pathlib import Path as FilePath
44
+ import asyncio
45
+
46
+
47
+ async def iter_bytesio(data: BytesIO, chunk_size: int = 8192):
48
+ data.seek(0)
49
+ while True:
50
+ chunk = data.read(chunk_size)
51
+ if not chunk:
52
+ break
53
+ try:
54
+ yield chunk
55
+ except (BrokenPipeError, ConnectionResetError):
56
+ return
57
+
58
+ def csv_entries_prepare_docs(query, docs_dicts, folder_views, keys_existence):
59
+ json_data = []
60
+ timestamp_fields = ["created_at", "updated_at"]
61
+ new_keys: set[str] = set()
62
+ deprecated_keys: set[str] = set()
63
+
64
+ for redis_document in docs_dicts:
65
+ rows: list[dict] = [{}]
66
+ flattened_doc = flatten_dict(redis_document)
67
+ for folder_view in folder_views:
68
+ column_key = folder_view.get("key")
69
+ column_title = folder_view.get("name")
70
+ attribute_val = flattened_doc.get(column_key)
71
+
72
+ if column_key.startswith('attachments.') and attribute_val is None:
73
+ parts = column_key.split('.')
74
+ if len(parts) >= 3:
75
+ attachment_type = parts[1]
76
+ property_name = '.'.join(parts[2:])
77
+
78
+ attachment_key = f"attachments.{attachment_type}"
79
+ attachments_array = flattened_doc.get(attachment_key)
80
+
81
+ if isinstance(attachments_array, list):
82
+ flattened_attachments = [
83
+ flatten_dict(attachment) if isinstance(attachment, dict) else attachment
84
+ for attachment in attachments_array
85
+ ]
86
+ attribute_val = [
87
+ flattened_attachment.get(property_name)
88
+ for flattened_attachment in flattened_attachments
89
+ if isinstance(flattened_attachment, dict) and flattened_attachment.get(
90
+ property_name) is not None
91
+ ]
92
+ attribute_val = [val for val in attribute_val if val is not None]
93
+
94
+ if attribute_val:
95
+ keys_existence[column_title] = True
96
+ """
97
+ Extract array items in a separate row per item
98
+ - list_new_rows = []
99
+ - for row in rows:
100
+ - for item in new_list[1:]:
101
+ - new_row = row
102
+ - add item attributes to the new_row
103
+ - list_new_rows.append(new_row)
104
+ - add new_list[0] attributes to row
105
+ -
106
+ - rows += list_new_rows
107
+ """
108
+ if isinstance(attribute_val, list) and len(attribute_val) > 0:
109
+ if isinstance(attribute_val[0], dict):
110
+ joined_values = []
111
+ for item in attribute_val:
112
+ if isinstance(item, dict):
113
+ item_values = [str(v) for v in item.values()]
114
+ joined_values.extend(item_values)
115
+ else:
116
+ joined_values.append(str(item))
117
+ new_col = "|".join(joined_values)
118
+ else:
119
+ new_col = "|".join(str(item) for item in attribute_val)
120
+
121
+ for row in rows:
122
+ row[column_title] = new_col
123
+
124
+ elif attribute_val and not isinstance(attribute_val, list):
125
+ new_col = attribute_val if column_key not in timestamp_fields else \
126
+ datetime.fromtimestamp(attribute_val).strftime(
127
+ '%Y-%m-%d %H:%M:%S')
128
+ for row in rows:
129
+ row[column_title] = new_col
130
+ json_data += rows
131
+
132
+ # Sort all entries from all schemas
133
+ if query.sort_by in core.Meta.model_fields and len(query.filter_schema_names) > 1:
134
+ json_data = sorted(
135
+ json_data,
136
+ key=lambda d: d[query.sort_by] if query.sort_by in d else "",
137
+ reverse=(query.sort_type == api.SortType.descending),
138
+ )
139
+
140
+ return json_data, deprecated_keys, new_keys
141
+
142
+
143
+ async def serve_request_create_check_access(request, record, owner_shortname):
144
+ if not await access_control.check_access(
145
+ user_shortname=owner_shortname,
146
+ space_name=request.space_name,
147
+ subpath=record.subpath,
148
+ resource_type=record.resource_type,
149
+ action_type=core.ActionType.create,
150
+ record_attributes=record.attributes,
151
+ ):
152
+ raise api.Exception(
153
+ status.HTTP_401_UNAUTHORIZED,
154
+ api.Error(
155
+ type="request",
156
+ code=InternalErrorCode.NOT_ALLOWED,
157
+ message="You don't have permission to this action [4]",
158
+ ),
159
+ )
160
+
161
+
162
+ async def send_sms_email_invitation(resource_obj, record):
163
+ # SMS Invitation
164
+ if not resource_obj.is_msisdn_verified and resource_obj.msisdn:
165
+ inv_link = await repository.store_user_invitation_token(
166
+ resource_obj, "SMS"
167
+ )
168
+ if inv_link:
169
+ await send_sms(
170
+ msisdn=record.attributes.get("msisdn", ""),
171
+ message=languages[
172
+ resource_obj.language
173
+ ]["invitation_message"].replace(
174
+ "{link}",
175
+ await repository.url_shortner(inv_link)
176
+ ),
177
+ )
178
+ # EMAIL Invitation
179
+ if not resource_obj.is_email_verified and resource_obj.email:
180
+ inv_link = await repository.store_user_invitation_token(
181
+ resource_obj, "EMAIL"
182
+ )
183
+ if inv_link:
184
+ await send_email(
185
+ from_address=settings.email_sender,
186
+ to_address=resource_obj.email,
187
+ message=generate_email_from_template(
188
+ "activation",
189
+ {
190
+ "link": await repository.url_shortner(
191
+ inv_link
192
+ ),
193
+ "name": record.attributes.get(
194
+ "displayname", {}
195
+ ).get("en", ""),
196
+ "shortname": resource_obj.shortname,
197
+ "msisdn": resource_obj.msisdn,
198
+ },
199
+ ),
200
+ subject=generate_subject("activation"),
201
+ )
202
+
203
+
204
+ def set_resource_object(record, resource_obj, is_internal):
205
+ if not is_internal or "created_at" not in record.attributes:
206
+ resource_obj.created_at = datetime.now()
207
+ resource_obj.updated_at = datetime.now()
208
+ body_shortname = record.shortname
209
+
210
+ separate_payload_data = None
211
+ if (
212
+ resource_obj.payload
213
+ and resource_obj.payload.content_type == ContentType.json
214
+ and resource_obj.payload.body is not None
215
+ ):
216
+ separate_payload_data = resource_obj.payload.body
217
+ if settings.active_data_db == 'file':
218
+ resource_obj.payload.body = body_shortname + (
219
+ ".json" if record.resource_type != ResourceType.log else ".jsonl"
220
+ )
221
+ return separate_payload_data, resource_obj
222
+
223
+
224
+ async def serve_request_create(request: api.Request, owner_shortname: str, token: str, is_internal: bool = False):
225
+ failed_records = []
226
+ records = []
227
+
228
+ async def process_record(record):
229
+ if record.subpath[0] != "/":
230
+ record.subpath = f"/{record.subpath}"
231
+ try:
232
+ if record.resource_type == ResourceType.space:
233
+ created = await serve_space_create(request, record, owner_shortname)
234
+ if created:
235
+ await db.initialize_spaces()
236
+ await access_control.load_permissions_and_roles()
237
+
238
+ await plugin_manager.after_action(
239
+ core.Event(
240
+ space_name=record.shortname,
241
+ subpath=record.subpath,
242
+ shortname=record.shortname,
243
+ action_type=core.ActionType.create,
244
+ resource_type=ResourceType.space,
245
+ user_shortname=owner_shortname,
246
+ )
247
+ )
248
+ return created.to_record(record.subpath, created.shortname, []), None
249
+
250
+ schema_shortname: str | None = None
251
+ if (
252
+ "payload" in record.attributes
253
+ and isinstance(record.attributes.get("payload", None), dict)
254
+ and "schema_shortname" in record.attributes["payload"]
255
+ ):
256
+ schema_shortname = record.attributes["payload"]["schema_shortname"]
257
+ await plugin_manager.before_action(
258
+ core.Event(
259
+ space_name=request.space_name,
260
+ subpath=record.subpath,
261
+ shortname=record.shortname,
262
+ action_type=core.ActionType.create,
263
+ schema_shortname=schema_shortname,
264
+ resource_type=record.resource_type,
265
+ user_shortname=owner_shortname,
266
+ )
267
+ )
268
+
269
+ await serve_request_create_check_access(request, record, owner_shortname)
270
+
271
+ if record.resource_type == ResourceType.ticket:
272
+ record = await set_init_state_for_record(record, request.space_name, owner_shortname)
273
+
274
+ shortname_exists = await db.is_entry_exist(
275
+ space_name=request.space_name,
276
+ subpath=record.subpath,
277
+ shortname=record.shortname,
278
+ resource_type=record.resource_type,
279
+ schema_shortname=record.attributes.get("schema_shortname", None),
280
+ )
281
+
282
+ if record.shortname != settings.auto_uuid_rule and shortname_exists:
283
+ raise api.Exception(
284
+ status.HTTP_400_BAD_REQUEST,
285
+ api.Error(
286
+ type="request",
287
+ code=InternalErrorCode.SHORTNAME_ALREADY_EXIST,
288
+ message=f"This shortname {record.shortname} already exists",
289
+ ),
290
+ )
291
+
292
+ await db.validate_uniqueness(
293
+ request.space_name, record, RequestType.create, owner_shortname
294
+ )
295
+
296
+ resource_obj = core.Meta.from_record(
297
+ record=record, owner_shortname=owner_shortname
298
+ )
299
+
300
+ separate_payload_data, resource_obj = set_resource_object(
301
+ record, resource_obj, is_internal
302
+ )
303
+
304
+ if (
305
+ resource_obj.payload
306
+ and resource_obj.payload.content_type == ContentType.json
307
+ and resource_obj.payload.schema_shortname
308
+ and isinstance(separate_payload_data, dict)
309
+ ):
310
+ await db.validate_payload_with_schema(
311
+ payload_data=separate_payload_data,
312
+ space_name=request.space_name,
313
+ schema_shortname=resource_obj.payload.schema_shortname,
314
+ )
315
+
316
+ await db.save(
317
+ request.space_name,
318
+ record.subpath,
319
+ resource_obj,
320
+ )
321
+
322
+ if isinstance(resource_obj, core.User):
323
+ await send_sms_email_invitation(resource_obj, record)
324
+
325
+ if separate_payload_data is not None and isinstance(
326
+ separate_payload_data, dict
327
+ ):
328
+ await db.update_payload(
329
+ request.space_name,
330
+ record.subpath,
331
+ resource_obj,
332
+ separate_payload_data,
333
+ owner_shortname,
334
+ )
335
+
336
+ rec = resource_obj.to_record(
337
+ record.subpath,
338
+ resource_obj.shortname,
339
+ [],
340
+ )
341
+ record.attributes["logged_in_user_token"] = token
342
+ await plugin_manager.after_action(
343
+ core.Event(
344
+ space_name=request.space_name,
345
+ subpath=record.subpath,
346
+ shortname=resource_obj.shortname,
347
+ action_type=core.ActionType.create,
348
+ schema_shortname=(
349
+ record.attributes["payload"].get("schema_shortname", None)
350
+ if record.attributes.get("payload")
351
+ else None
352
+ ),
353
+ resource_type=record.resource_type,
354
+ user_shortname=owner_shortname,
355
+ attributes=record.attributes,
356
+ )
357
+ )
358
+ return rec, None
359
+ except api.Exception as e:
360
+ return None, {
361
+ "record": record,
362
+ "error": e.error.message,
363
+ "error_code": e.error.code,
364
+ }
365
+
366
+ results = await asyncio.gather(*(process_record(r) for r in request.records))
367
+ for rec, failed in results:
368
+ if rec is not None:
369
+ records.append(rec)
370
+ if failed is not None:
371
+ failed_records.append(failed)
372
+
373
+ return records, failed_records
374
+
375
+
376
+ async def serve_request_update_fetch_payload(
377
+ old_resource_obj, record, request, resource_cls, schema_shortname
378
+ ):
379
+ old_resource_payload_body : dict[str, Any] = {}
380
+ old_version_flattend = flatten_dict(
381
+ old_resource_obj.model_dump()
382
+ )
383
+ if (
384
+ record.resource_type != ResourceType.log
385
+ and old_resource_obj.payload
386
+ and old_resource_obj.payload.content_type == ContentType.json
387
+ ):
388
+ try:
389
+ if isinstance(old_resource_obj.payload.body, str):
390
+ mybody = await db.load_resource_payload(
391
+ space_name=request.space_name,
392
+ subpath=record.subpath,
393
+ filename=old_resource_obj.payload.body,
394
+ class_type=resource_cls,
395
+ schema_shortname=schema_shortname,
396
+ )
397
+ else:
398
+ mybody = old_resource_obj.payload.body
399
+ old_resource_payload_body = mybody if mybody else {}
400
+ except api.Exception as e:
401
+ if request.request_type == api.RequestType.update:
402
+ raise e
403
+
404
+ old_version_flattend.pop("payload.body", None)
405
+ old_version_flattend.update(
406
+ flatten_dict(
407
+ {"payload.body": old_resource_payload_body}
408
+ )
409
+ )
410
+
411
+ return old_version_flattend, old_resource_payload_body
412
+
413
+
414
+ async def serve_request_update(request, owner_shortname: str):
415
+ records: list[core.Record] = []
416
+ failed_records: list[dict] = []
417
+
418
+ async def process_record(record):
419
+ try:
420
+ if record.subpath[0] != "/":
421
+ record.subpath = f"/{record.subpath}"
422
+
423
+ if record.resource_type == ResourceType.space:
424
+ history_diff = await serve_space_update(
425
+ request,
426
+ record,
427
+ owner_shortname,
428
+ )
429
+
430
+ await db.initialize_spaces()
431
+ await access_control.load_permissions_and_roles()
432
+ await plugin_manager.after_action(
433
+ core.Event(
434
+ space_name=record.shortname,
435
+ subpath=record.subpath,
436
+ shortname=record.shortname,
437
+ action_type=core.ActionType.update,
438
+ resource_type=ResourceType.space,
439
+ user_shortname=owner_shortname,
440
+ attributes={"history_diff": history_diff},
441
+ )
442
+ )
443
+ return record, None
444
+
445
+ record_schema_shortname = record.attributes.get("payload", {}).get(
446
+ "schema_shortname", None
447
+ ) if record.attributes.get("payload", {}) is not None else None
448
+ await plugin_manager.before_action(
449
+ core.Event(
450
+ space_name=request.space_name,
451
+ subpath=record.subpath,
452
+ shortname=record.shortname,
453
+ schema_shortname=record_schema_shortname,
454
+ action_type=core.ActionType.update,
455
+ resource_type=record.resource_type,
456
+ user_shortname=owner_shortname,
457
+ )
458
+ )
459
+
460
+ resource_cls = getattr(
461
+ sys.modules["models.core"], camel_case(
462
+ record.resource_type
463
+ )
464
+ )
465
+ old_resource_obj = await db.load(
466
+ space_name=request.space_name,
467
+ subpath=record.subpath,
468
+ shortname=record.shortname,
469
+ class_type=resource_cls,
470
+ user_shortname=owner_shortname,
471
+ schema_shortname=record_schema_shortname,
472
+ )
473
+
474
+ if settings.is_sha_required:
475
+ requested_checksum = record.attributes.get("last_checksum_history")
476
+ if requested_checksum:
477
+ latest_history = await db.get_latest_history(
478
+ space_name=request.space_name,
479
+ subpath=record.subpath,
480
+ shortname=record.shortname,
481
+ )
482
+ if latest_history and latest_history.last_checksum_history != requested_checksum:
483
+ raise api.Exception(
484
+ status.HTTP_409_CONFLICT,
485
+ api.Error(
486
+ type="request",
487
+ code=InternalErrorCode.CONFLICT,
488
+ message="Resource has been updated by another request!",
489
+ ),
490
+ )
491
+
492
+ # CHECK PERMISSION
493
+ if not await access_control.check_access(
494
+ user_shortname=owner_shortname,
495
+ space_name=request.space_name,
496
+ subpath=record.subpath,
497
+ resource_type=record.resource_type,
498
+ action_type=core.ActionType.update,
499
+ resource_is_active=old_resource_obj.is_active,
500
+ resource_owner_shortname=old_resource_obj.owner_shortname,
501
+ resource_owner_group=old_resource_obj.owner_group_shortname,
502
+ record_attributes=record.attributes,
503
+ entry_shortname=record.shortname
504
+ ):
505
+ raise api.Exception(
506
+ status.HTTP_401_UNAUTHORIZED,
507
+ api.Error(
508
+ type="request",
509
+ code=InternalErrorCode.NOT_ALLOWED,
510
+ message="You don't have permission to this action [5]",
511
+ ),
512
+ )
513
+
514
+ # GET PAYLOAD DATA
515
+ old_version_flattend, old_resource_payload_body = await serve_request_update_fetch_payload(
516
+ old_resource_obj, record, request, resource_cls, record_schema_shortname
517
+ )
518
+
519
+ # GENERATE NEW RESOURCE OBJECT
520
+ resource_obj = old_resource_obj
521
+ resource_obj.updated_at = datetime.now()
522
+
523
+ new_version_flattend = {}
524
+
525
+ if record.resource_type == ResourceType.log:
526
+ if payload := record.attributes.get("payload", {}):
527
+ new_resource_payload_data = payload.get("body", {})
528
+ else:
529
+ new_resource_payload_data = None
530
+ else:
531
+ if 'password' in record.attributes:
532
+ if 'old_password' not in record.attributes:
533
+ raise API_Exception(
534
+ status.HTTP_403_FORBIDDEN,
535
+ API_Error(
536
+ type="auth",
537
+ code=InternalErrorCode.PASSWORD_RESET_ERROR,
538
+ message="missing old_password!",
539
+ ),
540
+ )
541
+ else:
542
+ if not password_hashing.verify_password(record.attributes.get('old_password'), old_resource_obj.password):
543
+ raise API_Exception(
544
+ status.HTTP_403_FORBIDDEN,
545
+ API_Error(
546
+ type="auth",
547
+ code=InternalErrorCode.PASSWORD_RESET_ERROR,
548
+ message="Wrong password have been provided!",
549
+ ),
550
+ )
551
+ new_resource_payload_data = resource_obj.update_from_record(
552
+ record=record,
553
+ old_body=old_resource_payload_body,
554
+ )
555
+
556
+ new_version_flattend = resource_obj.model_dump()
557
+ if new_resource_payload_data:
558
+ new_version_flattend["payload"] = {
559
+ **new_version_flattend["payload"],
560
+ "body": new_resource_payload_data
561
+ }
562
+ new_version_flattend = flatten_dict(new_version_flattend)
563
+
564
+ await db.validate_uniqueness(
565
+ request.space_name, record, RequestType.update, owner_shortname
566
+ )
567
+ # VALIDATE SEPARATE PAYLOAD BODY
568
+ if (
569
+ resource_obj.payload
570
+ and resource_obj.payload.content_type == ContentType.json
571
+ and resource_obj.payload.schema_shortname
572
+ and new_resource_payload_data is not None
573
+ ):
574
+ await db.validate_payload_with_schema(
575
+ payload_data=new_resource_payload_data,
576
+ space_name=request.space_name,
577
+ schema_shortname=resource_obj.payload.schema_shortname,
578
+ )
579
+
580
+ if record.resource_type == ResourceType.log:
581
+ history_diff = await db.update(
582
+ space_name=request.space_name,
583
+ subpath=record.subpath,
584
+ meta=resource_obj,
585
+ old_version_flattend={},
586
+ new_version_flattend={},
587
+ updated_attributes_flattend=[],
588
+ user_shortname=owner_shortname,
589
+ schema_shortname=record_schema_shortname,
590
+ retrieve_lock_status=record.retrieve_lock_status,
591
+ )
592
+ else:
593
+ updated_attributes_flattend = list(
594
+ flatten_dict(record.attributes).keys()
595
+ )
596
+
597
+ if (settings.active_data_db == 'sql'
598
+ and new_resource_payload_data is not None
599
+ and resource_obj.payload
600
+ and resource_obj.payload.content_type == ContentType.json):
601
+ resource_obj.payload.body = new_resource_payload_data
602
+
603
+ history_diff = await db.update(
604
+ space_name=request.space_name,
605
+ subpath=record.subpath,
606
+ meta=resource_obj,
607
+ old_version_flattend=old_version_flattend,
608
+ new_version_flattend=new_version_flattend,
609
+ updated_attributes_flattend=updated_attributes_flattend,
610
+ user_shortname=owner_shortname,
611
+ schema_shortname=record_schema_shortname,
612
+ retrieve_lock_status=record.retrieve_lock_status,
613
+ )
614
+
615
+ if new_resource_payload_data is not None:
616
+ await db.save_payload_from_json(
617
+ request.space_name,
618
+ record.subpath,
619
+ resource_obj,
620
+ new_resource_payload_data,
621
+ )
622
+
623
+ if (
624
+ isinstance(resource_obj, core.User) and
625
+ (
626
+ record.attributes.get("is_active", None) is not None
627
+ or (
628
+ settings.logout_on_pwd_change and record.attributes.get("password", None) is not None
629
+ )
630
+ )
631
+ ):
632
+ if not record.attributes.get("is_active"):
633
+ await db.remove_user_session(record.shortname)
634
+
635
+ rec = resource_obj.to_record(
636
+ record.subpath, resource_obj.shortname, []
637
+ )
638
+
639
+ await plugin_manager.after_action(
640
+ core.Event(
641
+ space_name=request.space_name,
642
+ subpath=record.subpath,
643
+ shortname=record.shortname,
644
+ schema_shortname=record_schema_shortname,
645
+ action_type=core.ActionType.update,
646
+ resource_type=record.resource_type,
647
+ user_shortname=owner_shortname,
648
+ attributes={"history_diff": history_diff},
649
+ )
650
+ )
651
+ return rec, None
652
+ except api.Exception as e:
653
+ return None, {
654
+ "record": record,
655
+ "error": e.error.message,
656
+ "error_code": e.error.code,
657
+ }
658
+
659
+ results = await asyncio.gather(*(process_record(r) for r in request.records))
660
+ for rec, failed in results:
661
+ if rec is not None:
662
+ records.append(rec)
663
+ if failed is not None:
664
+ failed_records.append(failed)
665
+ return records, failed_records
666
+
667
+
668
+ async def serve_request_patch(request, owner_shortname: str):
669
+ records: list[core.Record] = []
670
+ failed_records: list[dict] = []
671
+
672
+ async def process_record(record):
673
+ try:
674
+ if record.subpath[0] != "/":
675
+ record.subpath = f"/{record.subpath}"
676
+
677
+ await plugin_manager.before_action(
678
+ core.Event(
679
+ space_name=request.space_name,
680
+ subpath=record.subpath,
681
+ shortname=record.shortname,
682
+ schema_shortname=record.attributes.get("payload", {}).get(
683
+ "schema_shortname", None
684
+ ),
685
+ action_type=core.ActionType.update,
686
+ resource_type=record.resource_type,
687
+ user_shortname=owner_shortname,
688
+ )
689
+ )
690
+
691
+ resource_cls = getattr(
692
+ sys.modules["models.core"], camel_case(
693
+ record.resource_type
694
+ )
695
+ )
696
+ schema_shortname = record.attributes.get("payload", {}).get(
697
+ "schema_shortname"
698
+ )
699
+ old_resource_obj = await db.load(
700
+ space_name=request.space_name,
701
+ subpath=record.subpath,
702
+ shortname=record.shortname,
703
+ class_type=resource_cls,
704
+ user_shortname=owner_shortname,
705
+ schema_shortname=schema_shortname,
706
+ )
707
+
708
+ # CHECK PERMISSION
709
+ if not await access_control.check_access(
710
+ user_shortname=owner_shortname,
711
+ space_name=request.space_name,
712
+ subpath=record.subpath,
713
+ resource_type=record.resource_type,
714
+ action_type=core.ActionType.update,
715
+ resource_is_active=old_resource_obj.is_active,
716
+ resource_owner_shortname=old_resource_obj.owner_shortname,
717
+ resource_owner_group=old_resource_obj.owner_group_shortname,
718
+ record_attributes=record.attributes,
719
+ entry_shortname=record.shortname
720
+ ):
721
+ raise api.Exception(
722
+ status.HTTP_401_UNAUTHORIZED,
723
+ api.Error(
724
+ type="request",
725
+ code=InternalErrorCode.NOT_ALLOWED,
726
+ message="You don't have permission to this action [8]",
727
+ ),
728
+ )
729
+
730
+ # GET PAYLOAD DATA
731
+ old_version_flattend, old_resource_payload_body = await serve_request_update_fetch_payload(
732
+ old_resource_obj, record, request, resource_cls, schema_shortname
733
+ )
734
+
735
+ # GENERATE NEW RESOURCE OBJECT
736
+ resource_obj = old_resource_obj
737
+ resource_obj.updated_at = datetime.now()
738
+
739
+ new_version_flattend = {}
740
+
741
+ if record.resource_type == ResourceType.log:
742
+ new_resource_payload_data = record.attributes.get("payload", {}).get(
743
+ "body", {}
744
+ )
745
+ else:
746
+ new_resource_payload_data = (
747
+ resource_obj.update_from_record(
748
+ record=record,
749
+ old_body=old_resource_payload_body,
750
+ )
751
+ )
752
+ new_version_flattend = resource_obj.model_dump()
753
+ if new_resource_payload_data:
754
+ new_version_flattend["payload"] = {
755
+ **new_version_flattend["payload"],
756
+ "body": new_resource_payload_data
757
+ }
758
+ new_version_flattend = flatten_dict(new_version_flattend)
759
+
760
+ await db.validate_uniqueness(
761
+ request.space_name, record, RequestType.update, owner_shortname
762
+ )
763
+
764
+ if record.resource_type == ResourceType.log:
765
+ history_diff = await db.update(
766
+ space_name=request.space_name,
767
+ subpath=record.subpath,
768
+ meta=resource_obj,
769
+ old_version_flattend={},
770
+ new_version_flattend={},
771
+ updated_attributes_flattend=[],
772
+ user_shortname=owner_shortname,
773
+ schema_shortname=schema_shortname,
774
+ retrieve_lock_status=record.retrieve_lock_status,
775
+ )
776
+ else:
777
+ updated_attributes_flattend = list(
778
+ flatten_dict(record.attributes).keys()
779
+ )
780
+
781
+ # VALIDATE SEPARATE PAYLOAD BODY
782
+ if (
783
+ resource_obj.payload
784
+ and resource_obj.payload.content_type == ContentType.json
785
+ and resource_obj.payload.schema_shortname
786
+ and new_resource_payload_data is not None
787
+ ):
788
+ await db.validate_payload_with_schema(
789
+ payload_data=new_resource_payload_data,
790
+ space_name=request.space_name,
791
+ schema_shortname=resource_obj.payload.schema_shortname,
792
+ )
793
+
794
+ history_diff = await db.update(
795
+ space_name=request.space_name,
796
+ subpath=record.subpath,
797
+ meta=resource_obj,
798
+ old_version_flattend=old_version_flattend,
799
+ new_version_flattend=new_version_flattend,
800
+ updated_attributes_flattend=updated_attributes_flattend,
801
+ user_shortname=owner_shortname,
802
+ schema_shortname=schema_shortname,
803
+ retrieve_lock_status=record.retrieve_lock_status,
804
+ )
805
+
806
+ if new_resource_payload_data is not None:
807
+ await db.save_payload_from_json(
808
+ request.space_name,
809
+ record.subpath,
810
+ resource_obj,
811
+ new_resource_payload_data,
812
+ )
813
+
814
+ if (
815
+ isinstance(resource_obj, core.User) and
816
+ record.attributes.get("is_active") is False
817
+ ):
818
+ await db.remove_user_session(record.shortname)
819
+ if resource_obj.payload and new_resource_payload_data:
820
+ resource_obj.payload.body = new_resource_payload_data
821
+ rec = resource_obj.to_record(
822
+ record.subpath, resource_obj.shortname, []
823
+ )
824
+
825
+ await plugin_manager.after_action(
826
+ core.Event(
827
+ space_name=request.space_name,
828
+ subpath=record.subpath,
829
+ shortname=record.shortname,
830
+ schema_shortname=record.attributes.get("payload", {}).get(
831
+ "schema_shortname", None
832
+ ),
833
+ action_type=core.ActionType.update,
834
+ resource_type=record.resource_type,
835
+ user_shortname=owner_shortname,
836
+ attributes={"history_diff": history_diff},
837
+ )
838
+ )
839
+ return rec, None
840
+ except api.Exception as e:
841
+ return None, {
842
+ "record": record,
843
+ "error": e.error.message,
844
+ "error_code": e.error.code,
845
+ }
846
+
847
+ results = await asyncio.gather(*(process_record(r) for r in request.records))
848
+ for rec, failed in results:
849
+ if rec is not None:
850
+ records.append(rec)
851
+ if failed is not None:
852
+ failed_records.append(failed)
853
+ return records, failed_records
854
+
855
+
856
+ async def serve_request_assign(request, owner_shortname: str):
857
+ records: list[core.Record] = []
858
+ failed_records: list[dict] = []
859
+
860
+ async def process_record(record):
861
+ try:
862
+ if not record.attributes.get("owner_shortname"):
863
+ raise api.Exception(
864
+ status.HTTP_400_BAD_REQUEST,
865
+ api.Error(
866
+ type="request",
867
+ code=InternalErrorCode.MISSING_DATA,
868
+ message="The owner_shortname is required",
869
+ ),
870
+ )
871
+ _target_user = await db.load(
872
+ space_name=settings.management_space,
873
+ subpath=settings.users_subpath,
874
+ shortname=record.attributes["owner_shortname"],
875
+ class_type=core.User,
876
+ )
877
+
878
+ if record.subpath[0] != "/":
879
+ record.subpath = f"/{record.subpath}"
880
+ await plugin_manager.before_action(
881
+ core.Event(
882
+ space_name=request.space_name,
883
+ subpath=record.subpath,
884
+ shortname=record.shortname,
885
+ schema_shortname=record.attributes.get("payload", {}).get(
886
+ "schema_shortname", None
887
+ ),
888
+ action_type=core.ActionType.update,
889
+ resource_type=record.resource_type,
890
+ user_shortname=owner_shortname,
891
+ )
892
+ )
893
+
894
+ resource_cls = getattr(
895
+ sys.modules["models.core"], camel_case(
896
+ record.resource_type)
897
+ )
898
+ schema_shortname = record.attributes.get("payload", {}).get(
899
+ "schema_shortname"
900
+ )
901
+ resource_obj = await db.load(
902
+ space_name=request.space_name,
903
+ subpath=record.subpath,
904
+ shortname=record.shortname,
905
+ class_type=resource_cls,
906
+ user_shortname=owner_shortname,
907
+ schema_shortname=schema_shortname,
908
+ )
909
+
910
+ # CHECK PERMISSION
911
+ if not await access_control.check_access(
912
+ user_shortname=owner_shortname,
913
+ space_name=request.space_name,
914
+ subpath=record.subpath,
915
+ resource_type=record.resource_type,
916
+ action_type=core.ActionType.assign,
917
+ resource_is_active=resource_obj.is_active,
918
+ resource_owner_shortname=resource_obj.owner_shortname,
919
+ resource_owner_group=resource_obj.owner_group_shortname,
920
+ record_attributes=record.attributes,
921
+ entry_shortname=record.shortname
922
+ ):
923
+ raise api.Exception(
924
+ status.HTTP_401_UNAUTHORIZED,
925
+ api.Error(
926
+ type="request",
927
+ code=InternalErrorCode.NOT_ALLOWED,
928
+ message="You don't have permission to this action [25]",
929
+ ),
930
+ )
931
+
932
+ old_version_flattend = flatten_dict(resource_obj.model_dump())
933
+
934
+ resource_obj.updated_at = datetime.now()
935
+ resource_obj.owner_shortname = record.attributes["owner_shortname"]
936
+
937
+ history_diff = await db.update(
938
+ space_name=request.space_name,
939
+ subpath=record.subpath,
940
+ meta=resource_obj,
941
+ old_version_flattend=old_version_flattend,
942
+ new_version_flattend=flatten_dict(resource_obj.model_dump()),
943
+ updated_attributes_flattend=["owner_shortname"],
944
+ user_shortname=owner_shortname,
945
+ schema_shortname=schema_shortname,
946
+ retrieve_lock_status=record.retrieve_lock_status,
947
+ )
948
+
949
+ rec = resource_obj.to_record(
950
+ record.subpath, resource_obj.shortname, []
951
+ )
952
+
953
+ await plugin_manager.after_action(
954
+ core.Event(
955
+ space_name=request.space_name,
956
+ subpath=record.subpath,
957
+ shortname=record.shortname,
958
+ schema_shortname=record.attributes.get("payload", {}).get(
959
+ "schema_shortname", None
960
+ ),
961
+ action_type=core.ActionType.update,
962
+ resource_type=record.resource_type,
963
+ user_shortname=owner_shortname,
964
+ attributes={"history_diff": history_diff},
965
+ )
966
+ )
967
+ return rec, None
968
+ except api.Exception as e:
969
+ return None, {
970
+ "record": record,
971
+ "error": e.error.message,
972
+ "error_code": e.error.code,
973
+ }
974
+
975
+ results = await asyncio.gather(*(process_record(r) for r in request.records))
976
+ for rec, failed in results:
977
+ if rec is not None:
978
+ records.append(rec)
979
+ if failed is not None:
980
+ failed_records.append(failed)
981
+
982
+ return records, failed_records
983
+
984
+
985
+ async def serve_request_update_acl(request, owner_shortname: str):
986
+ records: list[core.Record] = []
987
+ failed_records: list[dict] = []
988
+
989
+ async def process_record(record):
990
+ try:
991
+ if record.attributes.get("acl", None) is None:
992
+ raise api.Exception(
993
+ status.HTTP_400_BAD_REQUEST,
994
+ api.Error(
995
+ type="request",
996
+ code=InternalErrorCode.MISSING_DATA,
997
+ message="The acl is required",
998
+ ),
999
+ )
1000
+
1001
+ if record.subpath[0] != "/":
1002
+ record.subpath = f"/{record.subpath}"
1003
+ await plugin_manager.before_action(
1004
+ core.Event(
1005
+ space_name=request.space_name,
1006
+ subpath=record.subpath,
1007
+ shortname=record.shortname,
1008
+ schema_shortname=record.attributes.get("payload", {}).get(
1009
+ "schema_shortname", None
1010
+ ),
1011
+ action_type=core.ActionType.update,
1012
+ resource_type=record.resource_type,
1013
+ user_shortname=owner_shortname,
1014
+ )
1015
+ )
1016
+
1017
+ resource_cls = getattr(
1018
+ sys.modules["models.core"], camel_case(
1019
+ record.resource_type)
1020
+ )
1021
+ schema_shortname = record.attributes.get("payload", {}).get(
1022
+ "schema_shortname"
1023
+ )
1024
+ resource_obj = await db.load(
1025
+ space_name=request.space_name,
1026
+ subpath=record.subpath,
1027
+ shortname=record.shortname,
1028
+ class_type=resource_cls,
1029
+ user_shortname=owner_shortname,
1030
+ schema_shortname=schema_shortname,
1031
+ )
1032
+
1033
+ # CHECK PERMISSION
1034
+ if not await access_control.check_access(
1035
+ user_shortname=owner_shortname,
1036
+ space_name=request.space_name,
1037
+ subpath=record.subpath,
1038
+ resource_type=record.resource_type,
1039
+ action_type=core.ActionType.update,
1040
+ resource_is_active=resource_obj.is_active,
1041
+ resource_owner_shortname=resource_obj.owner_shortname,
1042
+ resource_owner_group=resource_obj.owner_group_shortname,
1043
+ record_attributes=record.attributes,
1044
+ entry_shortname=record.shortname
1045
+ ):
1046
+ raise api.Exception(
1047
+ status.HTTP_401_UNAUTHORIZED,
1048
+ api.Error(
1049
+ type="request",
1050
+ code=InternalErrorCode.NOT_ALLOWED,
1051
+ message="You don't have permission to this action [26]",
1052
+ ),
1053
+ )
1054
+
1055
+ old_version_flattend = flatten_dict(resource_obj.model_dump())
1056
+
1057
+ resource_obj.updated_at = datetime.now()
1058
+ resource_obj.acl = record.attributes["acl"]
1059
+
1060
+ history_diff = await db.update(
1061
+ space_name=request.space_name,
1062
+ subpath=record.subpath,
1063
+ meta=resource_obj,
1064
+ old_version_flattend=old_version_flattend,
1065
+ new_version_flattend=flatten_dict(resource_obj.model_dump()),
1066
+ updated_attributes_flattend=["acl"],
1067
+ user_shortname=owner_shortname,
1068
+ schema_shortname=schema_shortname,
1069
+ retrieve_lock_status=record.retrieve_lock_status,
1070
+ )
1071
+
1072
+ rec = resource_obj.to_record(
1073
+ record.subpath, resource_obj.shortname, []
1074
+ )
1075
+
1076
+ await plugin_manager.after_action(
1077
+ core.Event(
1078
+ space_name=request.space_name,
1079
+ subpath=record.subpath,
1080
+ shortname=record.shortname,
1081
+ schema_shortname=record.attributes.get("payload", {}).get(
1082
+ "schema_shortname", None
1083
+ ),
1084
+ action_type=core.ActionType.update,
1085
+ resource_type=record.resource_type,
1086
+ user_shortname=owner_shortname,
1087
+ attributes={"history_diff": history_diff},
1088
+ )
1089
+ )
1090
+ return rec, None
1091
+ except api.Exception as e:
1092
+ return None, {
1093
+ "record": record,
1094
+ "error": e.error.message,
1095
+ "error_code": e.error.code,
1096
+ }
1097
+
1098
+ results = await asyncio.gather(*(process_record(r) for r in request.records))
1099
+ for rec, failed in results:
1100
+ if rec is not None:
1101
+ records.append(rec)
1102
+ if failed is not None:
1103
+ failed_records.append(failed)
1104
+ return records, failed_records
1105
+
1106
+
1107
+ async def serve_request_delete(request, owner_shortname: str):
1108
+ records: list[core.Record] = []
1109
+ failed_records: list[dict] = []
1110
+
1111
+ async def process_record(record):
1112
+ try:
1113
+ if record.subpath[0] != "/":
1114
+ record.subpath = f"/{record.subpath}"
1115
+
1116
+ if record.resource_type == ResourceType.space:
1117
+ await serve_space_delete(request, record, owner_shortname)
1118
+ await db.initialize_spaces()
1119
+ await access_control.load_permissions_and_roles()
1120
+ await plugin_manager.after_action(
1121
+ core.Event(
1122
+ space_name=record.shortname,
1123
+ subpath=record.subpath,
1124
+ shortname=record.shortname,
1125
+ action_type=core.ActionType.delete,
1126
+ resource_type=ResourceType.space,
1127
+ user_shortname=owner_shortname,
1128
+ )
1129
+ )
1130
+ return record, None
1131
+
1132
+ await plugin_manager.before_action(
1133
+ core.Event(
1134
+ space_name=request.space_name,
1135
+ subpath=record.subpath,
1136
+ shortname=record.shortname,
1137
+ action_type=core.ActionType.delete,
1138
+ resource_type=record.resource_type,
1139
+ user_shortname=owner_shortname,
1140
+ )
1141
+ )
1142
+
1143
+ resource_cls = getattr(
1144
+ sys.modules["models.core"], camel_case(
1145
+ record.resource_type)
1146
+ )
1147
+ schema_shortname = record.attributes.get("payload", {}).get(
1148
+ "schema_shortname"
1149
+ )
1150
+ resource_obj = await db.load(
1151
+ space_name=request.space_name,
1152
+ subpath=record.subpath,
1153
+ shortname=record.shortname,
1154
+ class_type=resource_cls,
1155
+ user_shortname=owner_shortname,
1156
+ schema_shortname=schema_shortname,
1157
+ )
1158
+ if not await access_control.check_access(
1159
+ user_shortname=owner_shortname,
1160
+ space_name=request.space_name,
1161
+ subpath=record.subpath,
1162
+ resource_type=record.resource_type,
1163
+ action_type=core.ActionType.delete,
1164
+ resource_is_active=resource_obj.is_active,
1165
+ resource_owner_shortname=resource_obj.owner_shortname,
1166
+ resource_owner_group=resource_obj.owner_group_shortname,
1167
+ entry_shortname=record.shortname
1168
+ ):
1169
+ raise api.Exception(
1170
+ status.HTTP_401_UNAUTHORIZED,
1171
+ api.Error(
1172
+ type="request",
1173
+ code=InternalErrorCode.NOT_ALLOWED,
1174
+ message="You don't have permission to this action [6]",
1175
+ ),
1176
+ )
1177
+ try:
1178
+ await db.delete(
1179
+ space_name=request.space_name,
1180
+ subpath=record.subpath,
1181
+ meta=resource_obj,
1182
+ user_shortname=owner_shortname,
1183
+ schema_shortname=schema_shortname,
1184
+ retrieve_lock_status=record.retrieve_lock_status,
1185
+ )
1186
+ except api.Exception as e:
1187
+ return None, {
1188
+ "record": record,
1189
+ "error": e.error.message,
1190
+ "error_code": e.error.code,
1191
+ }
1192
+
1193
+ await plugin_manager.after_action(
1194
+ core.Event(
1195
+ space_name=request.space_name,
1196
+ subpath=record.subpath,
1197
+ shortname=record.shortname,
1198
+ action_type=core.ActionType.delete,
1199
+ resource_type=record.resource_type,
1200
+ user_shortname=owner_shortname,
1201
+ attributes={"entry": resource_obj},
1202
+ )
1203
+ )
1204
+
1205
+ return record, None
1206
+ except api.Exception as e:
1207
+ return None, {
1208
+ "record": record,
1209
+ "error": e.error.message,
1210
+ "error_code": e.error.code,
1211
+ }
1212
+
1213
+ results = await asyncio.gather(*(process_record(r) for r in request.records))
1214
+ for rec, failed in results:
1215
+ if rec is not None:
1216
+ records.append(rec)
1217
+ if failed is not None:
1218
+ failed_records.append(failed)
1219
+
1220
+ return records, failed_records
1221
+
1222
+
1223
+ async def serve_request_move(request, owner_shortname: str):
1224
+ records: list[core.Record] = []
1225
+ failed_records: list[dict] = []
1226
+
1227
+ async def process_record(record):
1228
+ try:
1229
+ if record.subpath[0] != "/":
1230
+ record.subpath = f"/{record.subpath}"
1231
+
1232
+ if (
1233
+ not record.attributes.get("src_space_name")
1234
+ or not record.attributes.get("src_subpath")
1235
+ or not record.attributes.get("src_shortname")
1236
+ or not record.attributes.get("dest_space_name")
1237
+ or not record.attributes.get("dest_subpath")
1238
+ or not record.attributes.get("dest_shortname")
1239
+ ):
1240
+ raise api.Exception(
1241
+ status.HTTP_400_BAD_REQUEST,
1242
+ api.Error(
1243
+ type="move",
1244
+ code=InternalErrorCode.PROVID_SOURCE_PATH,
1245
+ message="Please provide a source and destination path and a src shortname",
1246
+ ),
1247
+ )
1248
+
1249
+ await plugin_manager.before_action(
1250
+ core.Event(
1251
+ space_name=request.space_name,
1252
+ subpath=record.attributes["src_subpath"],
1253
+ shortname=record.attributes["src_shortname"],
1254
+ action_type=core.ActionType.move,
1255
+ resource_type=record.resource_type,
1256
+ user_shortname=owner_shortname,
1257
+ attributes={
1258
+ "dest_space_name": record.attributes["dest_space_name"],
1259
+ "dest_subpath": record.attributes["dest_subpath"]
1260
+ },
1261
+ )
1262
+ )
1263
+
1264
+ resource_cls = getattr(
1265
+ sys.modules["models.core"], camel_case(
1266
+ record.resource_type)
1267
+ )
1268
+ resource_obj = await db.load(
1269
+ space_name=request.space_name,
1270
+ subpath=record.attributes["src_subpath"],
1271
+ shortname=record.attributes["src_shortname"],
1272
+ class_type=resource_cls,
1273
+ user_shortname=owner_shortname,
1274
+ )
1275
+ check_src_subpath = await access_control.check_access(
1276
+ user_shortname=owner_shortname,
1277
+ space_name=request.space_name,
1278
+ subpath=record.attributes["src_subpath"],
1279
+ resource_type=record.resource_type,
1280
+ action_type=core.ActionType.delete,
1281
+ resource_is_active=resource_obj.is_active,
1282
+ resource_owner_shortname=resource_obj.owner_shortname,
1283
+ resource_owner_group=resource_obj.owner_group_shortname,
1284
+ entry_shortname=record.shortname
1285
+ )
1286
+ check_dest_subpath = await access_control.check_access(
1287
+ user_shortname=owner_shortname,
1288
+ space_name=request.space_name,
1289
+ subpath=record.attributes["dest_subpath"],
1290
+ resource_type=record.resource_type,
1291
+ action_type=core.ActionType.create,
1292
+ )
1293
+ if not check_src_subpath or not check_dest_subpath:
1294
+ raise api.Exception(
1295
+ status.HTTP_401_UNAUTHORIZED,
1296
+ api.Error(
1297
+ type="request",
1298
+ code=InternalErrorCode.NOT_ALLOWED,
1299
+ message="You don't have permission to this action [7]",
1300
+ ),
1301
+ )
1302
+
1303
+ try:
1304
+ await db.move(
1305
+ record.attributes["src_space_name"],
1306
+ record.attributes["src_subpath"],
1307
+ record.attributes["src_shortname"],
1308
+ record.attributes["dest_space_name"],
1309
+ record.attributes["dest_subpath"],
1310
+ record.attributes["dest_shortname"],
1311
+ resource_obj,
1312
+ )
1313
+ except api.Exception as e:
1314
+ return None, {
1315
+ "record": record,
1316
+ "error": e.error.message,
1317
+ "error_code": e.error.code,
1318
+ }
1319
+
1320
+ await plugin_manager.after_action(
1321
+ core.Event(
1322
+ space_name=request.space_name,
1323
+ subpath=record.attributes["dest_subpath"],
1324
+ shortname=record.attributes["dest_shortname"],
1325
+ action_type=core.ActionType.move,
1326
+ resource_type=record.resource_type,
1327
+ user_shortname=owner_shortname,
1328
+ attributes={
1329
+ "src_subpath": record.attributes["src_subpath"],
1330
+ "src_shortname": record.attributes["src_shortname"],
1331
+ },
1332
+ )
1333
+ )
1334
+
1335
+ return record, None
1336
+ except api.Exception as e:
1337
+ return None, {
1338
+ "record": record,
1339
+ "error": e.error.message,
1340
+ "error_code": e.error.code,
1341
+ }
1342
+
1343
+ results = await asyncio.gather(*(process_record(r) for r in request.records))
1344
+ for rec, failed in results:
1345
+ if rec is not None:
1346
+ records.append(rec)
1347
+ if failed is not None:
1348
+ failed_records.append(failed)
1349
+
1350
+ return records, failed_records
1351
+
1352
+
1353
+ def get_resource_content_type_from_payload_content_type(payload_file, payload_filename, record):
1354
+ if payload_filename.endswith(".json"):
1355
+ return ContentType.json
1356
+ elif payload_file.content_type == "application/pdf":
1357
+ return ContentType.pdf
1358
+ elif payload_file.content_type == "application/vnd.android.package-archive":
1359
+ return ContentType.apk
1360
+ elif payload_file.content_type == "text/csv":
1361
+ return ContentType.csv
1362
+ elif payload_file.content_type == "application/octet-stream":
1363
+ if record.attributes.get("content_type") == "jsonl":
1364
+ return ContentType.jsonl
1365
+ elif record.attributes.get("content_type") == "sqlite":
1366
+ return ContentType.sqlite
1367
+ elif record.attributes.get("content_type") == "parquet":
1368
+ return ContentType.parquet
1369
+ else:
1370
+ return ContentType.text
1371
+ elif payload_file.content_type == "text/markdown":
1372
+ return ContentType.markdown
1373
+ elif payload_file.content_type and "image/" in payload_file.content_type:
1374
+ return ContentType.image
1375
+ elif payload_file.content_type and "audio/" in payload_file.content_type:
1376
+ return ContentType.audio
1377
+ elif payload_file.content_type and "video/" in payload_file.content_type:
1378
+ return ContentType.video
1379
+ else:
1380
+ raise api.Exception(
1381
+ status.HTTP_406_NOT_ACCEPTABLE,
1382
+ api.Error(
1383
+ type="attachment",
1384
+ code=InternalErrorCode.NOT_SUPPORTED_TYPE,
1385
+ message="The file type is not supported",
1386
+ ),
1387
+ )
1388
+
1389
+
1390
+ async def handle_update_state(space_name, logged_in_user, ticket_obj, action, user_roles):
1391
+ workflows_data = await db.load(
1392
+ space_name=space_name,
1393
+ subpath="workflows",
1394
+ shortname=ticket_obj.workflow_shortname,
1395
+ class_type=core.Content,
1396
+ user_shortname=logged_in_user,
1397
+ )
1398
+
1399
+ workflows_payload: Any = {}
1400
+ if workflows_data.payload is not None and workflows_data.payload.body is not None:
1401
+ if settings.active_data_db == 'file':
1402
+ workflows_payload = await db.load_resource_payload(
1403
+ space_name=space_name,
1404
+ subpath="workflows",
1405
+ filename=str(workflows_data.payload.body),
1406
+ class_type=core.Content,
1407
+ )
1408
+ else:
1409
+ workflows_payload = workflows_data.payload.body
1410
+ else:
1411
+ raise api.Exception(
1412
+ status_code=status.HTTP_400_BAD_REQUEST,
1413
+ error=api.Error(
1414
+ type="transition",
1415
+ code=InternalErrorCode.MISSING_DATA,
1416
+ message="Invalid workflow",
1417
+ ),
1418
+ )
1419
+
1420
+ if not ticket_obj.is_open:
1421
+ raise api.Exception(
1422
+ status_code=status.HTTP_400_BAD_REQUEST,
1423
+ error=api.Error(
1424
+ type="transition",
1425
+ code=InternalErrorCode.TICKET_ALREADY_CLOSED,
1426
+ message="Ticket is already in closed",
1427
+ ),
1428
+ )
1429
+ response = transite(
1430
+ workflows_payload.get("states", []), ticket_obj.state, action, user_roles
1431
+ )
1432
+
1433
+ if not response.get("status", False):
1434
+ raise api.Exception(
1435
+ status_code=status.HTTP_400_BAD_REQUEST,
1436
+ error=api.Error(
1437
+ type="transition",
1438
+ code=InternalErrorCode.INVALID_TICKET_STATUS,
1439
+ message=response.get("message", "")
1440
+ ),
1441
+ )
1442
+
1443
+ old_version_flattend = flatten_dict(ticket_obj.model_dump())
1444
+
1445
+ ticket_obj.state = response["message"]
1446
+ ticket_obj.is_open = check_open_state(
1447
+ workflows_payload.get("states", []), response["message"]
1448
+ )
1449
+
1450
+ return ticket_obj, workflows_payload, response, old_version_flattend
1451
+
1452
+
1453
+ async def update_state_handle_resolution(ticket_obj, workflows_payload, response, resolution):
1454
+ post_response = post_transite(
1455
+ workflows_payload["states"], response["message"], resolution
1456
+ )
1457
+ if not post_response["status"]:
1458
+ raise api.Exception(
1459
+ status_code=status.HTTP_400_BAD_REQUEST,
1460
+ error=api.Error(
1461
+ type="transition",
1462
+ code=InternalErrorCode.INVALID_TICKET_STATUS,
1463
+ message=post_response["message"],
1464
+ ),
1465
+ )
1466
+ ticket_obj.resolution_reason = resolution
1467
+ return ticket_obj
1468
+
1469
+
1470
+ async def serve_space_create(request, record, owner_shortname: str):
1471
+ await is_space_exist(request.space_name, should_exist=False)
1472
+
1473
+ if not await access_control.check_access(
1474
+ user_shortname=owner_shortname,
1475
+ space_name=settings.all_spaces_mw,
1476
+ subpath="/",
1477
+ resource_type=ResourceType.space,
1478
+ action_type=core.ActionType.create,
1479
+ record_attributes=record.attributes,
1480
+ ):
1481
+ raise api.Exception(
1482
+ status.HTTP_401_UNAUTHORIZED,
1483
+ api.Error(
1484
+ type="request",
1485
+ code=InternalErrorCode.NOT_ALLOWED,
1486
+ message="You don't have permission to this action [1]",
1487
+ ),
1488
+ )
1489
+
1490
+ resource_obj = core.Meta.from_record(
1491
+ record=record, owner_shortname=owner_shortname
1492
+ )
1493
+ resource_obj.is_active = True
1494
+ resource_obj.shortname = request.space_name
1495
+ if isinstance(resource_obj, core.Space):
1496
+ resource_obj.indexing_enabled = True
1497
+ resource_obj.active_plugins = [
1498
+ "action_log",
1499
+ "redis_db_update",
1500
+ "resource_folders_creation",
1501
+ ]
1502
+
1503
+ return await db.save(
1504
+ request.space_name,
1505
+ record.subpath,
1506
+ resource_obj,
1507
+ )
1508
+
1509
+
1510
+ async def serve_space_update(request, record, owner_shortname: str, is_replace: bool = False):
1511
+ try:
1512
+ space = core.Space.from_record(record, owner_shortname)
1513
+ await is_space_exist(request.space_name)
1514
+
1515
+ if (
1516
+ request.space_name != record.shortname
1517
+ ):
1518
+ raise Exception
1519
+ except Exception:
1520
+ raise api.Exception(
1521
+ status.HTTP_400_BAD_REQUEST,
1522
+ api.Error(
1523
+ type="request",
1524
+ code=InternalErrorCode.INVALID_SPACE_NAME,
1525
+ message=f"Space name {request.space_name} provided is empty or invalid [6]",
1526
+ ),
1527
+ )
1528
+ if not await access_control.check_access(
1529
+ user_shortname=owner_shortname,
1530
+ space_name=settings.all_spaces_mw,
1531
+ subpath="/",
1532
+ resource_type=ResourceType.space,
1533
+ action_type=core.ActionType.update,
1534
+ record_attributes=record.attributes,
1535
+ entry_shortname=record.shortname
1536
+ ):
1537
+ raise api.Exception(
1538
+ status.HTTP_401_UNAUTHORIZED,
1539
+ api.Error(
1540
+ type="request",
1541
+ code=InternalErrorCode.NOT_ALLOWED,
1542
+ message="You don't have permission to this action [2]",
1543
+ ),
1544
+ )
1545
+
1546
+ await plugin_manager.before_action(
1547
+ core.Event(
1548
+ space_name=space.shortname,
1549
+ subpath=record.subpath,
1550
+ shortname=space.shortname,
1551
+ action_type=core.ActionType.update,
1552
+ resource_type=record.resource_type,
1553
+ user_shortname=owner_shortname,
1554
+ )
1555
+ )
1556
+
1557
+ old_space = await db.load(
1558
+ space_name=space.shortname,
1559
+ subpath=record.subpath,
1560
+ shortname=space.shortname,
1561
+ class_type=core.Space,
1562
+ user_shortname=owner_shortname,
1563
+ )
1564
+
1565
+ if settings.is_sha_required:
1566
+ requested_checksum = record.attributes.get("last_checksum_history")
1567
+ if requested_checksum:
1568
+ latest_history = await db.get_latest_history(
1569
+ space_name=space.shortname,
1570
+ subpath=record.subpath,
1571
+ shortname=space.shortname,
1572
+ )
1573
+ if latest_history and latest_history.last_checksum_history != requested_checksum:
1574
+ raise api.Exception(
1575
+ status.HTTP_409_CONFLICT,
1576
+ api.Error(
1577
+ type="request",
1578
+ code=InternalErrorCode.CONFLICT,
1579
+ message="Resource has been updated by another request. Please refresh and try again.",
1580
+ ),
1581
+ )
1582
+
1583
+ old_flat = flatten_dict(old_space.model_dump())
1584
+ new_flat = flatten_dict(space.model_dump())
1585
+ updated_attributes_flattend = list(
1586
+ flatten_dict(record.attributes).keys()
1587
+ )
1588
+ if is_replace:
1589
+ updated_attributes_flattend = list(old_flat.keys()) + list(new_flat.keys())
1590
+ history_diff = await db.update(
1591
+ space_name=space.shortname,
1592
+ subpath=record.subpath,
1593
+ meta=space,
1594
+ old_version_flattend=old_flat,
1595
+ new_version_flattend=new_flat,
1596
+ updated_attributes_flattend=updated_attributes_flattend,
1597
+ user_shortname=owner_shortname,
1598
+ retrieve_lock_status=record.retrieve_lock_status,
1599
+ )
1600
+ return history_diff
1601
+
1602
+
1603
+ async def serve_space_delete(request, record, owner_shortname: str):
1604
+ if request.space_name == "management":
1605
+ raise api.Exception(
1606
+ status.HTTP_400_BAD_REQUEST,
1607
+ api.Error(
1608
+ type="request",
1609
+ code=InternalErrorCode.CANNT_DELETE,
1610
+ message="Cannot delete management space",
1611
+ ),
1612
+ )
1613
+
1614
+ await is_space_exist(request.space_name)
1615
+
1616
+ if not await access_control.check_access(
1617
+ user_shortname=owner_shortname,
1618
+ space_name=settings.all_spaces_mw,
1619
+ subpath="/",
1620
+ resource_type=ResourceType.space,
1621
+ action_type=core.ActionType.delete,
1622
+ entry_shortname=record.shortname
1623
+ ):
1624
+ raise api.Exception(
1625
+ status.HTTP_401_UNAUTHORIZED,
1626
+ api.Error(
1627
+ type="request",
1628
+ code=InternalErrorCode.NOT_ALLOWED,
1629
+ message="You don't have permission to this action [3]",
1630
+ ),
1631
+ )
1632
+ await repository.delete_space(request.space_name, record, owner_shortname)
1633
+ await db.drop_index(request.space_name)
1634
+
1635
+
1636
+
1637
+ async def data_asset_attachments_handler(query, attachments):
1638
+ files_paths = []
1639
+ for attachment in attachments.get(query.data_asset_type, []):
1640
+ file_path = db.payload_path(
1641
+ space_name=query.space_name,
1642
+ subpath=f"{query.subpath}/{query.shortname}",
1643
+ class_type=getattr(sys.modules["models.core"], camel_case(query.data_asset_type)),
1644
+ )
1645
+ if (
1646
+ not isinstance(attachment.attributes.get("payload"), core.Payload)
1647
+ or not isinstance(attachment.attributes["payload"].body, str)
1648
+ or not (file_path / attachment.attributes["payload"].body).is_file()
1649
+ ):
1650
+ raise api.Exception(
1651
+ status_code=status.HTTP_404_NOT_FOUND,
1652
+ error=api.Error(
1653
+ type="db",
1654
+ code=InternalErrorCode.INVALID_DATA,
1655
+ message=f"Invalid data asset file found at {attachment.subpath}/{attachment.shortname}",
1656
+ ),
1657
+ )
1658
+
1659
+ file_path /= attachment.attributes["payload"].body
1660
+ if (
1661
+ attachment.attributes["payload"].schema_shortname
1662
+ and attachment.resource_type == DataAssetType.csv
1663
+ ):
1664
+ await validate_csv_with_schema(
1665
+ file_path=file_path,
1666
+ space_name=query.space_name,
1667
+ schema_shortname=attachment.attributes["payload"].schema_shortname
1668
+ )
1669
+ if (
1670
+ attachment.attributes["payload"].schema_shortname
1671
+ and attachment.resource_type == DataAssetType.jsonl
1672
+ ):
1673
+ await validate_jsonl_with_schema(
1674
+ file_path=file_path,
1675
+ space_name=query.space_name,
1676
+ schema_shortname=attachment.attributes["payload"].schema_shortname
1677
+ )
1678
+ files_paths.append(file_path)
1679
+ return files_paths
1680
+
1681
+
1682
+ async def data_asset_handler(conn, query, files_paths, attachments):
1683
+ for idx, file_path in enumerate(files_paths):
1684
+ # Load the file into the in-memory DB
1685
+ match query.data_asset_type:
1686
+ case DataAssetType.csv:
1687
+ globals().setdefault(
1688
+ attachments[query.data_asset_type][idx].shortname,
1689
+ conn.read_csv(str(file_path))
1690
+ )
1691
+ case DataAssetType.jsonl:
1692
+ globals().setdefault(
1693
+ attachments[query.data_asset_type][idx].shortname,
1694
+ conn.read_json(
1695
+ str(file_path),
1696
+ format='auto'
1697
+ )
1698
+ )
1699
+ case DataAssetType.parquet:
1700
+ globals().setdefault(
1701
+ attachments[query.data_asset_type][idx].shortname,
1702
+ conn.read_parquet(str(file_path))
1703
+ )
1704
+
1705
+
1706
+ async def import_resources_from_csv_handler(
1707
+ row, meta_class_attributes, schema_content, data_types_mapper,
1708
+ ):
1709
+ shortname = ""
1710
+ meta_object = {}
1711
+ payload_object = {}
1712
+ for key, value in row.items():
1713
+ if not key or not value:
1714
+ continue
1715
+
1716
+ if key == "shortname":
1717
+ shortname = value
1718
+ continue
1719
+
1720
+ keys_list = [i.strip() for i in key.split(".")]
1721
+ if keys_list[0] in meta_class_attributes:
1722
+ match len(keys_list):
1723
+ case 1:
1724
+ if str(meta_class_attributes[keys_list[0]].annotation).startswith('list'):
1725
+ meta_object[keys_list[0].strip()] = [
1726
+ e.strip().strip("'").strip('"') for e in value.strip("[]").split(",")
1727
+ ]
1728
+ else:
1729
+ meta_object[keys_list[0].strip()] = value
1730
+ case 2:
1731
+ if keys_list[0].strip() not in meta_object:
1732
+ meta_object[keys_list[0].strip()] = []
1733
+ meta_object[keys_list[0].strip(
1734
+ )][keys_list[1].strip()] = value
1735
+ continue
1736
+
1737
+ if schema_content is not None:
1738
+ current_schema_property = schema_content
1739
+ for item in keys_list:
1740
+ if "oneOf" in current_schema_property:
1741
+ for oneOf_item in current_schema_property["oneOf"]:
1742
+ if (
1743
+ "properties" in oneOf_item
1744
+ and item.strip() in oneOf_item["properties"]
1745
+ ):
1746
+ current_schema_property = oneOf_item["properties"][
1747
+ item.strip()
1748
+ ]
1749
+ break
1750
+ else:
1751
+ if (
1752
+ "properties" in current_schema_property
1753
+ and item.strip() in current_schema_property["properties"]
1754
+ ):
1755
+ current_schema_property = current_schema_property["properties"][
1756
+ item.strip()
1757
+ ]
1758
+
1759
+ if current_schema_property["type"] in ["number", "integer"]:
1760
+ value = value.replace(",", "")
1761
+ try:
1762
+ value = data_types_mapper[current_schema_property["type"]](value)
1763
+ if current_schema_property["type"] == "array":
1764
+ value = [
1765
+ str(item) if type(item) in [int, float] else item for item in value
1766
+ ]
1767
+ except ValueError as e:
1768
+ raise api.Exception(
1769
+ status.HTTP_400_BAD_REQUEST,
1770
+ api.Error(
1771
+ type="request",
1772
+ code=InternalErrorCode.INVALID_DATA,
1773
+ message=f"Invalid value for {key}: {value}",
1774
+ info=[{"message": str(e)}],
1775
+ ),
1776
+ )
1777
+
1778
+ match len(keys_list):
1779
+ case 1:
1780
+ payload_object[keys_list[0].strip()] = value
1781
+ case 2:
1782
+ if keys_list[0].strip() not in payload_object:
1783
+ payload_object[keys_list[0].strip()] = {}
1784
+ payload_object[keys_list[0].strip(
1785
+ )][keys_list[1].strip()] = value
1786
+ case 3:
1787
+ if keys_list[0].strip() not in payload_object:
1788
+ payload_object[keys_list[0].strip()] = {}
1789
+ if keys_list[1].strip() not in payload_object[keys_list[0].strip()]:
1790
+ payload_object[keys_list[0].strip(
1791
+ )][keys_list[1].strip()] = {}
1792
+ payload_object[keys_list[0].strip()][keys_list[1].strip()][
1793
+ keys_list[2].strip()
1794
+ ] = value
1795
+ case _:
1796
+ continue
1797
+ if shortname == "":
1798
+ shortname = settings.auto_uuid_rule
1799
+ return payload_object, meta_object, shortname
1800
+
1801
+
1802
+ async def create_or_update_resource_with_payload_handler(
1803
+ record, owner_shortname, space_name, payload_file, payload_filename, checksum, sha, resource_content_type
1804
+ ):
1805
+ if record.resource_type == ResourceType.ticket:
1806
+ record = await set_init_state_from_record(
1807
+ record, owner_shortname, space_name
1808
+ )
1809
+ resource_obj = core.Meta.from_record(
1810
+ record=record, owner_shortname=owner_shortname)
1811
+ if record.resource_type == ResourceType.ticket:
1812
+ record = await set_init_state_from_record(
1813
+ record, owner_shortname, space_name
1814
+ )
1815
+
1816
+ file_extension = FilePath(payload_filename).suffix
1817
+ if file_extension.startswith('.'):
1818
+ file_extension = file_extension[1:]
1819
+
1820
+ resource_obj.payload = core.Payload(
1821
+ content_type=resource_content_type,
1822
+ checksum=checksum,
1823
+ client_checksum=sha if isinstance(sha, str) else None,
1824
+ schema_shortname="meta_schema"
1825
+ if record.resource_type == ResourceType.schema
1826
+ else record.attributes.get("payload", {}).get("schema_shortname", None),
1827
+ body=f"{record.shortname}.{file_extension}",
1828
+ )
1829
+ if (
1830
+ not isinstance(resource_obj, core.Attachment)
1831
+ and not isinstance(resource_obj, core.Content)
1832
+ and not isinstance(resource_obj, core.Ticket)
1833
+ and not isinstance(resource_obj, core.Schema)
1834
+ ):
1835
+ raise api.Exception(
1836
+ status.HTTP_400_BAD_REQUEST,
1837
+ api.Error(
1838
+ type="attachment",
1839
+ code=InternalErrorCode.SOME_SUPPORTED_TYPE,
1840
+ message="Only resources of type 'attachment' or 'content' are allowed",
1841
+ ),
1842
+ )
1843
+ if settings.active_data_db == "file":
1844
+ resource_obj.payload.body = f"{resource_obj.shortname}.{file_extension}"
1845
+ elif not isinstance(resource_obj, core.Attachment):
1846
+ resource_obj.payload.body = json.load(payload_file.file)
1847
+ payload_file.file.seek(0)
1848
+
1849
+ if (
1850
+ resource_content_type == ContentType.json
1851
+ and resource_obj.payload.schema_shortname
1852
+ ):
1853
+ await db.validate_payload_with_schema(
1854
+ payload_data=payload_file,
1855
+ space_name=space_name,
1856
+ schema_shortname=resource_obj.payload.schema_shortname,
1857
+ )
1858
+
1859
+ return resource_obj, record
1860
+
1861
+
1862
+ def get_mime_type(content_type: ContentType) -> str:
1863
+ mime_types = {
1864
+ ContentType.text: "text/plain",
1865
+ ContentType.markdown: "text/markdown",
1866
+ ContentType.html: "text/html",
1867
+ ContentType.json: "application/json",
1868
+ ContentType.image: "image/jpeg",
1869
+ ContentType.python: "text/x-python",
1870
+ ContentType.pdf: "application/pdf",
1871
+ ContentType.audio: "audio/mpeg",
1872
+ ContentType.video: "video/mp4",
1873
+ ContentType.csv: "text/csv",
1874
+ ContentType.parquet: "application/octet-stream",
1875
+ ContentType.jsonl: "application/jsonlines",
1876
+ ContentType.duckdb: "application/octet-stream",
1877
+ ContentType.sqlite: "application/vnd.sqlite3"
1878
+ }
1879
+ return mime_types.get(content_type, "application/octet-stream")