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,1541 @@
1
+ import csv
2
+ import hashlib
3
+ import json
4
+ import os
5
+ import re
6
+ import sys
7
+ import tempfile
8
+ import traceback
9
+ import zipfile
10
+ import codecs
11
+ from datetime import datetime
12
+ from io import StringIO, BytesIO
13
+ from pathlib import Path as FilePath
14
+ from re import sub as res_sub
15
+ # from time import time
16
+ from typing import Any, Callable
17
+ from fastapi import APIRouter, Body, Depends, Form, Path, Query, UploadFile, status
18
+ from fastapi.responses import RedirectResponse, ORJSONResponse
19
+ from starlette.responses import FileResponse, StreamingResponse
20
+
21
+ import models.api as api
22
+ import models.core as core
23
+ import utils.regex as regex
24
+ import utils.repository as repository
25
+ from api.managed.utils import (
26
+ create_or_update_resource_with_payload_handler,
27
+ csv_entries_prepare_docs,
28
+ # data_asset_attachments_handler,
29
+ # data_asset_handler,
30
+ get_mime_type,
31
+ get_resource_content_type_from_payload_content_type,
32
+ handle_update_state,
33
+ import_resources_from_csv_handler,
34
+ serve_request_assign,
35
+ serve_request_create,
36
+ serve_request_delete,
37
+ serve_request_move,
38
+ serve_request_update_acl,
39
+ serve_request_patch,
40
+ serve_request_update,
41
+ update_state_handle_resolution,
42
+ iter_bytesio
43
+ )
44
+ from data_adapters.adapter import data_adapter as db
45
+ from models.enums import (
46
+ ContentType,
47
+ # DataAssetType,
48
+ LockAction,
49
+ RequestType,
50
+ ResourceType,
51
+ TaskType,
52
+ )
53
+ from utils.access_control import access_control
54
+ from utils.helpers import (
55
+ camel_case,
56
+ # csv_file_to_json,
57
+ flatten_dict,
58
+ )
59
+ from utils.internal_error_code import InternalErrorCode
60
+ from utils.jwt import GetJWTToken, JWTBearer
61
+ from utils.plugin_manager import plugin_manager
62
+ from utils.router_helper import is_space_exist
63
+ from utils.settings import settings
64
+ from data_adapters.sql.json_to_db_migration import main as json_to_db_main
65
+
66
+ router = APIRouter(default_response_class=ORJSONResponse)
67
+
68
+ @router.post(
69
+ "/import",
70
+ response_model=api.Response,
71
+ response_model_exclude_none=True
72
+ )
73
+ async def import_data(
74
+ zip_file: UploadFile,
75
+ extra: str|None=None,
76
+ owner_shortname=Depends(JWTBearer()),
77
+ ):
78
+ with tempfile.TemporaryDirectory() as temp_dir:
79
+ try:
80
+ content = await zip_file.read()
81
+
82
+ zip_bytes = BytesIO(content)
83
+
84
+ with zipfile.ZipFile(zip_bytes, 'r') as zip_ref:
85
+ zip_ref.extractall(temp_dir)
86
+
87
+ original_spaces_folder = settings.spaces_folder
88
+ settings.spaces_folder = FilePath(temp_dir)
89
+
90
+ try:
91
+ await json_to_db_main()
92
+
93
+ return api.Response(
94
+ status=api.Status.success,
95
+ attributes={"message": "Data imported successfully"}
96
+ )
97
+ finally:
98
+ settings.spaces_folder = original_spaces_folder
99
+
100
+ except Exception as e:
101
+ return api.Response(
102
+ status=api.Status.failed,
103
+ attributes={"message": f"Failed to import data: {str(e)}"}
104
+ )
105
+
106
+ @router.post("/export", response_class=StreamingResponse)
107
+ async def export_data(query: api.Query, user_shortname=Depends(JWTBearer())):
108
+ with tempfile.TemporaryDirectory() as temp_dir:
109
+ try:
110
+ original_spaces_folder = settings.spaces_folder
111
+ temp_spaces_folder = FilePath(temp_dir)
112
+
113
+ zip_buffer = BytesIO()
114
+
115
+ try:
116
+ settings.spaces_folder = temp_spaces_folder
117
+
118
+ from data_adapters.sql.db_to_json_migration import export_data_with_query
119
+ await export_data_with_query(query, user_shortname)
120
+
121
+ with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
122
+ for root, _, files in os.walk(temp_dir):
123
+ for file in files:
124
+ file_path = os.path.join(root, file)
125
+ arcname = os.path.relpath(file_path, temp_dir)
126
+ zip_file.write(file_path, arcname)
127
+
128
+ zip_buffer.seek(0)
129
+
130
+ response = StreamingResponse(
131
+ iter([zip_buffer.getvalue()]),
132
+ media_type="application/zip"
133
+ )
134
+ response.headers["Content-Disposition"] = "attachment; filename=export.zip"
135
+
136
+ return response
137
+ finally:
138
+ settings.spaces_folder = original_spaces_folder
139
+
140
+ except Exception as e:
141
+ traceback.print_exc()
142
+ print(f"Export error: {e}")
143
+ return api.Response(
144
+ status=api.Status.failed,
145
+ attributes={"message": f"Failed to export data: {str(e)}"}
146
+ )
147
+
148
+
149
+ @router.post(
150
+ "/csv/{space_name}",
151
+ response_model=api.Response,
152
+ response_model_exclude_none=True,
153
+ )
154
+ async def generate_csv_from_report_saved_query(
155
+ space_name: str, record: core.Record, user_shortname=Depends(JWTBearer())
156
+ ):
157
+ records = (
158
+ await execute(
159
+ space_name=space_name,
160
+ record=record,
161
+ task_type=TaskType.query,
162
+ logged_in_user=user_shortname,
163
+ )
164
+ ).records
165
+ if not records:
166
+ raise api.Exception(
167
+ status.HTTP_400_BAD_REQUEST,
168
+ error=api.Error(
169
+ type="media", code=InternalErrorCode.OBJECT_NOT_FOUND, message="Request object is not available"
170
+ ),
171
+ )
172
+
173
+ json_data = []
174
+ for r in records:
175
+ if r.attributes is None:
176
+ continue
177
+ json_data.append(flatten_dict(r.attributes))
178
+
179
+ v_path = StringIO()
180
+
181
+ keys: set = set({})
182
+ for row in json_data:
183
+ keys.update(set(row.keys()))
184
+
185
+ writer = csv.DictWriter(v_path, fieldnames=list(keys))
186
+ writer.writeheader()
187
+ writer.writerows(json_data)
188
+
189
+ response = StreamingResponse(
190
+ iter([v_path.getvalue()]), media_type="text/csv")
191
+ response.headers[
192
+ "Content-Disposition"
193
+ ] = f"attachment; filename={space_name}_{record.subpath}.csv"
194
+
195
+ await plugin_manager.after_action(
196
+ core.Event(
197
+ space_name=space_name,
198
+ subpath=record.subpath,
199
+ action_type=core.ActionType.query,
200
+ user_shortname=user_shortname,
201
+ attributes={
202
+ "shortname": record.shortname,
203
+ "number_of_records": len(json_data),
204
+ },
205
+ )
206
+ )
207
+
208
+ return response
209
+
210
+
211
+ @router.post("/csv", response_model=api.Response, response_model_exclude_none=True)
212
+ async def csv_entries(query: api.Query, user_shortname=Depends(JWTBearer())):
213
+ await plugin_manager.before_action(
214
+ core.Event(
215
+ space_name=query.space_name,
216
+ subpath=query.subpath,
217
+ action_type=core.ActionType.query,
218
+ user_shortname=user_shortname,
219
+ attributes={"filter_shortnames": query.filter_shortnames},
220
+ )
221
+ )
222
+
223
+ query.retrieve_attachments=True
224
+ folder = await db.load(
225
+ query.space_name,
226
+ '/',
227
+ query.subpath,
228
+ core.Folder,
229
+ user_shortname,
230
+ )
231
+
232
+ folder_payload = await db.load_resource_payload(
233
+ query.space_name,
234
+ "/",
235
+ f"{folder.shortname}.json",
236
+ core.Folder,
237
+ )
238
+ folder_views: list = []
239
+ if folder_payload:
240
+ folder_views = folder_payload.get("csv_columns", [])
241
+ if not folder_views:
242
+ folder_views = folder_payload.get("index_attributes", [])
243
+
244
+ keys: list = [i["name"] for i in folder_views]
245
+ keys_existence = dict(zip(keys, [False for _ in range(len(keys))]))
246
+
247
+ # if settings.active_data_db == 'file':
248
+ # _, search_res = await db.query(query, user_shortname)
249
+ # else:
250
+ # _, search_res = await db.query(query, user_shortname)
251
+ # docs_dicts = [search_re.model_dump() for search_re in search_res]
252
+
253
+ _, search_res = await repository.serve_query(
254
+ query, user_shortname,
255
+ )
256
+
257
+ docs_dicts = [search_re.model_dump() for search_re in search_res]
258
+
259
+ json_data, deprecated_keys, new_keys = csv_entries_prepare_docs(query, docs_dicts, folder_views, keys_existence)
260
+
261
+ await plugin_manager.after_action(
262
+ core.Event(
263
+ space_name=query.space_name,
264
+ subpath=query.subpath,
265
+ action_type=core.ActionType.query,
266
+ user_shortname=user_shortname,
267
+ )
268
+ )
269
+
270
+ keys = [key for key in keys if keys_existence[key]]
271
+ v_path = StringIO()
272
+ v_path.write(codecs.BOM_UTF8.decode('utf-8'))
273
+ v_path.write(codecs.BOM_UTF8.decode('utf-8'))
274
+
275
+ list_deprecated_keys = list(deprecated_keys)
276
+ keys = list(filter(lambda item: item not in list_deprecated_keys, keys))
277
+ writer = csv.DictWriter(v_path, fieldnames=(keys + list(new_keys)))
278
+ writer.writeheader()
279
+ writer.writerows(json_data)
280
+
281
+ response = StreamingResponse(
282
+ iter([v_path.getvalue()]), media_type="text/csv")
283
+ response.headers[
284
+ "Content-Disposition"
285
+ ] = f"attachment; filename={query.space_name}_{query.subpath}.csv"
286
+
287
+ return response
288
+
289
+
290
+ # @router.post("/space", response_model=api.Response, response_model_exclude_none=True)
291
+ # async def serve_space(
292
+ # request: api.Request, owner_shortname=Depends(JWTBearer())
293
+ # ) -> api.Response:
294
+ # record = request.records[0]
295
+ # history_diff = {}
296
+ # _record = None
297
+ # match request.request_type:
298
+ # case api.RequestType.create:
299
+ # _record = await serve_space_create(request, record, owner_shortname)
300
+ #
301
+ # case api.RequestType.update:
302
+ # history_diff = await serve_space_update(request, record, owner_shortname)
303
+ #
304
+ # case api.RequestType.delete:
305
+ # await serve_space_delete(request, record, owner_shortname)
306
+ #
307
+ # case _:
308
+ # raise api.Exception(
309
+ # status.HTTP_400_BAD_REQUEST,
310
+ # api.Error(
311
+ # type="request",
312
+ # code=InternalErrorCode.UNMATCHED_DATA,
313
+ # message="mismatch with the information provided",
314
+ # ),
315
+ # )
316
+ #
317
+ # await db.initialize_spaces()
318
+ #
319
+ # await access_control.load_permissions_and_roles()
320
+ #
321
+ # await plugin_manager.after_action(
322
+ # core.Event(
323
+ # space_name=record.shortname,
324
+ # subpath=record.subpath,
325
+ # shortname=record.shortname,
326
+ # action_type=core.ActionType(request.request_type),
327
+ # resource_type=ResourceType.space,
328
+ # user_shortname=owner_shortname,
329
+ # attributes={"history_diff": history_diff},
330
+ # )
331
+ # )
332
+ #
333
+ # return api.Response(status=api.Status.success, records=[_record if _record else record])
334
+
335
+
336
+ @router.post("/query", response_model=api.Response, response_model_exclude_none=True)
337
+ async def query_entries(
338
+ query: api.Query, user_shortname=Depends(JWTBearer())
339
+ ) -> api.Response:
340
+ await is_space_exist(query.space_name)
341
+
342
+ await plugin_manager.before_action(
343
+ core.Event(
344
+ space_name=query.space_name,
345
+ subpath=query.subpath,
346
+ action_type=core.ActionType.query,
347
+ user_shortname=user_shortname,
348
+ attributes={"filter_shortnames": query.filter_shortnames},
349
+ )
350
+ )
351
+
352
+ total, records = await repository.serve_query(
353
+ query, user_shortname,
354
+ )
355
+
356
+ await plugin_manager.after_action(
357
+ core.Event(
358
+ space_name=query.space_name,
359
+ subpath=query.subpath,
360
+ action_type=core.ActionType.query,
361
+ user_shortname=user_shortname,
362
+ )
363
+ )
364
+ return api.Response(
365
+ status=api.Status.success,
366
+ records=records,
367
+ attributes={"total": total, "returned": len(records)},
368
+ )
369
+
370
+
371
+ @router.post("/request", response_model=api.Response, response_model_exclude_none=True)
372
+ async def serve_request(
373
+ request: api.Request,
374
+ token=Depends(GetJWTToken()),
375
+ owner_shortname=Depends(JWTBearer()),
376
+ is_internal: bool = False,
377
+ ) -> api.Response:
378
+ for r in request.records:
379
+ await is_space_exist(request.space_name, not (request.request_type == RequestType.create and r.resource_type == ResourceType.space))
380
+
381
+ if not request.records:
382
+ raise api.Exception(
383
+ status.HTTP_400_BAD_REQUEST,
384
+ api.Error(
385
+ type="request",
386
+ code=InternalErrorCode.MISSING_DATA,
387
+ message="Request records cannot be empty",
388
+ ),
389
+ )
390
+ records = []
391
+ failed_records = []
392
+ match request.request_type:
393
+ case api.RequestType.create:
394
+ records, failed_records = await serve_request_create(request, owner_shortname, token, is_internal)
395
+
396
+ case api.RequestType.update:
397
+ records, failed_records = await serve_request_update(request, owner_shortname)
398
+
399
+ case api.RequestType.assign:
400
+ records, failed_records = await serve_request_assign(request, owner_shortname)
401
+
402
+ case api.RequestType.update_acl:
403
+ records, failed_records = await serve_request_update_acl(request, owner_shortname)
404
+
405
+ case api.RequestType.patch:
406
+ records, failed_records = await serve_request_patch(request, owner_shortname)
407
+
408
+ case api.RequestType.delete:
409
+ records, failed_records = await serve_request_delete(request, owner_shortname)
410
+
411
+ case api.RequestType.move:
412
+ records, failed_records = await serve_request_move(request, owner_shortname)
413
+
414
+ if len(failed_records) == 0:
415
+ return api.Response(status=api.Status.success, records=records)
416
+ else:
417
+ raise api.Exception(
418
+ status_code=400,
419
+ error=api.Error(
420
+ type="request",
421
+ code=InternalErrorCode.SOMETHING_WRONG,
422
+ message="Something went wrong",
423
+ info=[{"successfull": records, "failed": failed_records}],
424
+ ),
425
+ )
426
+
427
+
428
+ @router.put(
429
+ "/progress-ticket/{space_name}/{subpath:path}/{shortname}/{action}",
430
+ response_model=api.Response,
431
+ response_model_exclude_none=True,
432
+ )
433
+ async def update_state(
434
+ logged_in_user=Depends(JWTBearer()),
435
+ space_name: str = Path(..., pattern=regex.SPACENAME, examples=["data"]),
436
+ subpath: str = Path(..., pattern=regex.SUBPATH, examples=["/content"]),
437
+ shortname: str = Path(..., pattern=regex.SHORTNAME,
438
+ examples=["unique_shortname"]),
439
+ action: str = Path(..., examples=["approve"]),
440
+ resolution: str | None = Body(None, embed=True, examples=[
441
+ "Ticket state resolution"]),
442
+ comment: str | None = Body(None, embed=True, examples=["Nice ticket"]),
443
+ retrieve_lock_status: bool | None = False,
444
+ ) -> api.Response:
445
+ await is_space_exist(space_name)
446
+
447
+ _user_roles = await db.get_user_roles(logged_in_user)
448
+ user_roles = _user_roles.keys()
449
+
450
+ await plugin_manager.before_action(
451
+ core.Event(
452
+ space_name=space_name,
453
+ subpath=subpath,
454
+ shortname=shortname,
455
+ action_type=core.ActionType.progress_ticket,
456
+ resource_type=ResourceType.ticket,
457
+ user_shortname=logged_in_user,
458
+ )
459
+ )
460
+ ticket_obj: core.Ticket = await db.load(
461
+ space_name=space_name,
462
+ subpath=subpath,
463
+ shortname=shortname,
464
+ class_type=core.Ticket,
465
+ user_shortname=logged_in_user,
466
+ )
467
+
468
+ if not await access_control.check_access(
469
+ user_shortname=logged_in_user,
470
+ space_name=space_name,
471
+ subpath=subpath,
472
+ resource_type=ResourceType.ticket,
473
+ action_type=core.ActionType.progress_ticket,
474
+ resource_is_active=ticket_obj.is_active,
475
+ resource_owner_shortname=ticket_obj.owner_shortname,
476
+ resource_owner_group=ticket_obj.owner_group_shortname,
477
+ record_attributes={"state": "", "resolution_reason": ""},
478
+ entry_shortname=shortname
479
+ ):
480
+ raise api.Exception(
481
+ status.HTTP_401_UNAUTHORIZED,
482
+ api.Error(
483
+ type="request",
484
+ code=InternalErrorCode.NOT_ALLOWED,
485
+ message="You don't have permission to this action [38]",
486
+ ),
487
+ )
488
+
489
+ workflows_data = await db.load(
490
+ space_name=space_name,
491
+ subpath="workflows",
492
+ shortname=ticket_obj.workflow_shortname,
493
+ class_type=core.Content,
494
+ user_shortname=logged_in_user,
495
+ )
496
+
497
+ if (
498
+ workflows_data.payload is not None
499
+ and workflows_data.payload.body is not None
500
+ ):
501
+ ticket_obj, workflows_payload, response, old_version_flattend = await handle_update_state(
502
+ space_name, logged_in_user, ticket_obj, action, user_roles
503
+ )
504
+ if resolution:
505
+ ticket_obj = await update_state_handle_resolution(ticket_obj, workflows_payload, response, resolution)
506
+
507
+ new_version_flattend = flatten_dict(ticket_obj.model_dump())
508
+ # new_version_flattend.pop("payload.body", None)
509
+ # new_version_flattend.update(
510
+ # flatten_dict({"payload.body": ticket_obj.model_dump(mode='json')}))
511
+
512
+ if comment:
513
+ time = datetime.now().strftime("%Y%m%d%H%M%S")
514
+ new_version_flattend["comment"] = comment
515
+ await serve_request(
516
+ api.Request(
517
+ space_name=space_name,
518
+ request_type=RequestType.create,
519
+ records=[
520
+ core.Record(
521
+ shortname=f"c_{time}",
522
+ subpath=f"{subpath}/{shortname}",
523
+ resource_type=ResourceType.comment,
524
+ attributes={
525
+ "is_active": True,
526
+ "payload": {
527
+ "content_type": ContentType.comment,
528
+ "body": {
529
+ "body": comment,
530
+ "state": ticket_obj.state,
531
+ }
532
+ },
533
+ },
534
+ )
535
+ ],
536
+ ),
537
+ owner_shortname=logged_in_user,
538
+ )
539
+
540
+ history_diff = await db.update(
541
+ space_name,
542
+ f"/{subpath}",
543
+ ticket_obj,
544
+ old_version_flattend,
545
+ new_version_flattend,
546
+ ["state", "resolution_reason", "comment"],
547
+ logged_in_user,
548
+ retrieve_lock_status=retrieve_lock_status,
549
+ )
550
+ await plugin_manager.after_action(
551
+ core.Event(
552
+ space_name=space_name,
553
+ subpath=subpath,
554
+ shortname=shortname,
555
+ action_type=core.ActionType.progress_ticket,
556
+ resource_type=ResourceType.ticket,
557
+ user_shortname=logged_in_user,
558
+ attributes={
559
+ "history_diff": history_diff,
560
+ "state": ticket_obj.state,
561
+ },
562
+ )
563
+ )
564
+ return api.Response(status=api.Status.success)
565
+
566
+ raise api.Exception(
567
+ status.HTTP_400_BAD_REQUEST,
568
+ error=api.Error(
569
+ type="ticket",
570
+ code=InternalErrorCode.WORKFLOW_BODY_NOT_FOUND,
571
+ message="Workflow body not found"
572
+ ),
573
+ )
574
+
575
+
576
+ @router.get(
577
+ "/payload/{resource_type}/{space_name}/{subpath:path}/{shortname}.{ext}",
578
+ response_model_exclude_none=True,
579
+ )
580
+ @router.get(
581
+ "/payload/{resource_type}/{space_name}/{subpath:path}/{shortname}.{schema_shortname}.{ext}",
582
+ response_model_exclude_none=True,
583
+ )
584
+ async def retrieve_entry_or_attachment_payload(
585
+ resource_type: ResourceType,
586
+ space_name: str = Path(..., pattern=regex.SPACENAME, examples=["data"]),
587
+ subpath: str = Path(..., pattern=regex.SUBPATH, examples=["/content"]),
588
+ shortname: str = Path(..., pattern=regex.SHORTNAME,
589
+ examples=["unique_shortname"]),
590
+ schema_shortname: str | None = None,
591
+ ext: str = Path(..., pattern=regex.EXT, examples=["png"]),
592
+ logged_in_user=Depends(JWTBearer()),
593
+ ) -> Any:
594
+ await plugin_manager.before_action(
595
+ core.Event(
596
+ space_name=space_name,
597
+ subpath=subpath,
598
+ shortname=shortname,
599
+ action_type=core.ActionType.view,
600
+ resource_type=resource_type,
601
+ user_shortname=logged_in_user,
602
+ )
603
+ )
604
+
605
+ cls = getattr(sys.modules["models.core"], camel_case(resource_type))
606
+ meta = await db.load(
607
+ space_name=space_name,
608
+ subpath=subpath,
609
+ shortname=shortname,
610
+ class_type=cls,
611
+ user_shortname=logged_in_user,
612
+ schema_shortname=schema_shortname,
613
+ )
614
+
615
+ if(
616
+ resource_type is not ResourceType.json
617
+ and (meta.payload is None
618
+ or meta.payload.body is None
619
+ or meta.payload.body != f"{shortname}.{ext}")
620
+ ):
621
+ raise api.Exception(
622
+ status.HTTP_400_BAD_REQUEST,
623
+ error=api.Error(
624
+ type="media", code=InternalErrorCode.OBJECT_NOT_FOUND, message="Request object is not available"
625
+ ),
626
+ )
627
+
628
+ if not await access_control.check_access(
629
+ user_shortname=logged_in_user,
630
+ space_name=space_name,
631
+ subpath=subpath,
632
+ resource_type=resource_type,
633
+ action_type=core.ActionType.view,
634
+ resource_is_active=meta.is_active,
635
+ resource_owner_shortname=meta.owner_shortname,
636
+ resource_owner_group=meta.owner_group_shortname,
637
+ entry_shortname=meta.shortname
638
+ ):
639
+ raise api.Exception(
640
+ status.HTTP_401_UNAUTHORIZED,
641
+ api.Error(
642
+ type="request",
643
+ code=InternalErrorCode.NOT_ALLOWED,
644
+ message="You don't have permission to this action [39]",
645
+ ),
646
+ )
647
+
648
+ await plugin_manager.after_action(
649
+ core.Event(
650
+ space_name=space_name,
651
+ subpath=subpath,
652
+ shortname=shortname,
653
+ action_type=core.ActionType.view,
654
+ resource_type=resource_type,
655
+ user_shortname=logged_in_user,
656
+ )
657
+ )
658
+
659
+ if settings.active_data_db == "file":
660
+ payload_path = db.payload_path(
661
+ space_name=space_name,
662
+ subpath=subpath,
663
+ class_type=cls,
664
+ schema_shortname=schema_shortname,
665
+ )
666
+ return FileResponse(payload_path / str(meta.payload.body))
667
+
668
+ if meta.payload.content_type == ContentType.json and isinstance(meta.payload.body, dict):
669
+ return api.Response(
670
+ status=api.Status.success,
671
+ attributes=meta.payload.body,
672
+ )
673
+
674
+ data: BytesIO | None = await db.get_media_attachment(space_name, subpath, shortname)
675
+ if data:
676
+ if meta.payload.body.endswith(".svg"):
677
+ mime_type = "image/svg+xml"
678
+ else:
679
+ mime_type = get_mime_type(meta.payload.content_type)
680
+ return StreamingResponse(iter_bytesio(data), media_type=mime_type)
681
+ return api.Response(status=api.Status.failed)
682
+
683
+ @router.post(
684
+ "/resource_with_payload",
685
+ response_model=api.Response,
686
+ response_model_exclude_none=True,
687
+ )
688
+ async def create_or_update_resource_with_payload(
689
+ payload_file: UploadFile,
690
+ request_record: UploadFile,
691
+ space_name: str = Form(..., examples=["data"]),
692
+ sha: str | None = Form(None, examples=["data"]),
693
+ owner_shortname: str = Depends(JWTBearer()),
694
+ ):
695
+ # NOTE We currently make no distinction between create and update.
696
+ # in such case update should contain all the data every time.
697
+ await is_space_exist(space_name)
698
+
699
+ record = core.Record.model_validate_json(request_record.file.read())
700
+
701
+ payload_filename = payload_file.filename or ""
702
+ if payload_filename and not re.search(regex.EXT, os.path.splitext(payload_filename)[1][1:]):
703
+ raise api.Exception(
704
+ status.HTTP_400_BAD_REQUEST,
705
+ api.Error(
706
+ type="request",
707
+ code=InternalErrorCode.INVALID_DATA,
708
+ message=f"Invalid payload file extention, it should end with {regex.EXT}",
709
+ ),
710
+ )
711
+ resource_content_type = get_resource_content_type_from_payload_content_type(
712
+ payload_file, payload_filename, record
713
+ )
714
+
715
+ await plugin_manager.before_action(
716
+ core.Event(
717
+ space_name=space_name,
718
+ subpath=record.subpath,
719
+ shortname=record.shortname,
720
+ action_type=core.ActionType.create,
721
+ schema_shortname=record.attributes.get("payload", {}).get(
722
+ "schema_shortname", None
723
+ ),
724
+ resource_type=record.resource_type,
725
+ user_shortname=owner_shortname,
726
+ )
727
+ )
728
+
729
+ if not await access_control.check_access(
730
+ user_shortname=owner_shortname,
731
+ space_name=space_name,
732
+ subpath=record.subpath,
733
+ resource_type=record.resource_type,
734
+ action_type=core.ActionType.create,
735
+ record_attributes=record.attributes,
736
+ entry_shortname=record.shortname,
737
+ ):
738
+ raise api.Exception(
739
+ status.HTTP_401_UNAUTHORIZED,
740
+ api.Error(
741
+ type="request",
742
+ code=InternalErrorCode.NOT_ALLOWED,
743
+ message="You don't have permission to this action [10]",
744
+ ),
745
+ )
746
+
747
+ sha1 = hashlib.sha1()
748
+ sha1.update(payload_file.file.read())
749
+ checksum = sha1.hexdigest()
750
+ if isinstance(sha, str) and sha != checksum:
751
+ raise api.Exception(
752
+ status.HTTP_400_BAD_REQUEST,
753
+ api.Error(
754
+ type="request",
755
+ code=InternalErrorCode.INVALID_DATA,
756
+ message="The provided file doesn't match the sha",
757
+ ),
758
+ )
759
+ await payload_file.seek(0)
760
+ resource_obj, record = await create_or_update_resource_with_payload_handler(
761
+ record, owner_shortname, space_name, payload_file, payload_filename, checksum, sha, resource_content_type
762
+ )
763
+ try:
764
+ _attachement = await db.load(
765
+ space_name,
766
+ record.subpath,
767
+ record.shortname,
768
+ getattr(sys.modules["models.core"], camel_case(record.resource_type)),
769
+ owner_shortname
770
+ )
771
+
772
+ resource_meta = core.Meta.from_record(
773
+ record=record, owner_shortname=owner_shortname
774
+ )
775
+ resource_meta.payload = resource_obj.payload
776
+
777
+ if resource_obj.payload and isinstance(resource_obj.payload.body, dict):
778
+ await db.update_payload(
779
+ space_name,
780
+ record.subpath,
781
+ resource_meta,
782
+ resource_obj.payload.body,
783
+ owner_shortname
784
+ )
785
+ await db.save_payload(
786
+ space_name, record.subpath, resource_obj, payload_file
787
+ )
788
+ except api.Exception as e:
789
+ if e.error.code == InternalErrorCode.OBJECT_NOT_FOUND:
790
+ await db.save(space_name, record.subpath, resource_obj)
791
+ await db.save_payload(
792
+ space_name, record.subpath, resource_obj, payload_file
793
+ )
794
+
795
+ await plugin_manager.after_action(
796
+ core.Event(
797
+ space_name=space_name,
798
+ subpath=record.subpath,
799
+ shortname=record.shortname,
800
+ action_type=core.ActionType.create,
801
+ schema_shortname=record.attributes.get("payload", {}).get(
802
+ "schema_shortname", None
803
+ ),
804
+ resource_type=record.resource_type,
805
+ user_shortname=owner_shortname,
806
+ )
807
+ )
808
+
809
+ return api.Response(
810
+ status=api.Status.success,
811
+ records=[record],
812
+ )
813
+
814
+
815
+ @router.post(
816
+ "/resources_from_csv/{resource_type}/{space_name}/{subpath:path}/{schema_shortname}",
817
+ response_model=api.Response,
818
+ response_model_exclude_none=True,
819
+ )
820
+ async def import_resources_from_csv(
821
+ resources_file: UploadFile,
822
+ resource_type: ResourceType,
823
+ space_name: str = Path(..., pattern=regex.SPACENAME, examples=["data"]),
824
+ subpath: str = Path(..., pattern=regex.SUBPATH, examples=["/content"]),
825
+ schema_shortname = None,
826
+ is_update: bool = False,
827
+ owner_shortname=Depends(JWTBearer()),
828
+ ):
829
+ contents = await resources_file.read()
830
+ decoded = contents.decode()
831
+ buffer = StringIO(decoded)
832
+ csv_reader = csv.DictReader(buffer)
833
+
834
+ schema_content = None
835
+ if schema_shortname:
836
+ schema_content = await db.get_schema(space_name, schema_shortname, owner_shortname)
837
+
838
+ data_types_mapper: dict[str, Callable] = {
839
+ "integer": int,
840
+ "number": float,
841
+ "string": str,
842
+ "boolean": bool,
843
+ "object": json.loads,
844
+ "array": json.loads,
845
+ }
846
+
847
+ resource_cls = getattr(
848
+ sys.modules["models.core"], camel_case(resource_type)
849
+ )
850
+ meta_class_attributes = resource_cls.model_fields
851
+ failed_shortnames: list = []
852
+ success_count = 0
853
+ for row in csv_reader:
854
+ payload_object, meta_object, shortname = await import_resources_from_csv_handler(
855
+ row,
856
+ meta_class_attributes,
857
+ schema_content,
858
+ data_types_mapper,
859
+ )
860
+
861
+ if "is_active" not in meta_object:
862
+ meta_object["is_active"] = True
863
+
864
+ attributes = meta_object
865
+
866
+ attributes["payload"] = {
867
+ "content_type": ContentType.json,
868
+ "body": payload_object,
869
+ }
870
+
871
+ if schema_shortname:
872
+ attributes["payload"]["schema_shortname"] = schema_shortname
873
+
874
+ record = core.Record(
875
+ resource_type=resource_type,
876
+ shortname=shortname,
877
+ subpath=subpath,
878
+ attributes=attributes,
879
+ )
880
+ try:
881
+ await serve_request(
882
+ request=api.Request(
883
+ space_name=space_name,
884
+ request_type=RequestType.update if is_update else RequestType.create,
885
+ records=[record],
886
+ ),
887
+ owner_shortname=owner_shortname,
888
+ is_internal=True,
889
+ )
890
+ success_count += 1
891
+ except api.Exception as e:
892
+ err = {shortname: e.__str__()}
893
+ if hasattr(e, "error"):
894
+ err["error"] = str(e.error)
895
+ failed_shortnames.append(err)
896
+ except Exception as e:
897
+ failed_shortnames.append({shortname: e.__str__()})
898
+
899
+ return api.Response(
900
+ status=api.Status.success,
901
+ attributes={
902
+ "success_count": success_count,
903
+ "failed_shortnames": failed_shortnames,
904
+ },
905
+ )
906
+
907
+
908
+ @router.get(
909
+ "/entry/{resource_type}/{space_name}/{subpath:path}/{shortname}",
910
+ response_model_exclude_none=True,
911
+ )
912
+ async def retrieve_entry_meta(
913
+ resource_type: ResourceType,
914
+ space_name: str = Path(..., pattern=regex.SPACENAME, examples=["data"]),
915
+ subpath: str = Path(..., pattern=regex.SUBPATH, examples=["/content"]),
916
+ shortname: str = Path(..., pattern=regex.SHORTNAME,
917
+ examples=["unique_shortname"]),
918
+ retrieve_json_payload: bool = False,
919
+ retrieve_attachments: bool = False,
920
+ filter_attachments_types: list = Query(default=[], examples=["media", "comment", "json"]),
921
+ validate_schema: bool = True,
922
+ logged_in_user=Depends(JWTBearer()),
923
+ ) -> dict[str, Any]:
924
+ if subpath == settings.root_subpath_mw:
925
+ subpath = "/"
926
+
927
+ await plugin_manager.before_action(
928
+ core.Event(
929
+ space_name=space_name,
930
+ subpath=subpath,
931
+ shortname=shortname,
932
+ action_type=core.ActionType.view,
933
+ resource_type=resource_type,
934
+ user_shortname=logged_in_user,
935
+ )
936
+ )
937
+
938
+ resource_class = getattr(
939
+ sys.modules["models.core"], camel_case(resource_type)
940
+ )
941
+ meta: core.Meta = await db.load(
942
+ space_name=space_name,
943
+ subpath=subpath,
944
+ shortname=shortname,
945
+ class_type=resource_class,
946
+ user_shortname=logged_in_user,
947
+ )
948
+ if meta is None:
949
+ raise api.Exception(
950
+ status.HTTP_400_BAD_REQUEST,
951
+ error=api.Error(
952
+ type="media",
953
+ code=InternalErrorCode.OBJECT_NOT_FOUND,
954
+ message="Request object is not available"
955
+ ),
956
+ )
957
+
958
+ if not await access_control.check_access(
959
+ user_shortname=logged_in_user,
960
+ space_name=space_name,
961
+ subpath=subpath,
962
+ resource_type=resource_type,
963
+ action_type=core.ActionType.view,
964
+ resource_is_active=meta.is_active,
965
+ resource_owner_shortname=meta.owner_shortname,
966
+ resource_owner_group=meta.owner_group_shortname,
967
+ entry_shortname=meta.shortname
968
+ ):
969
+ raise api.Exception(
970
+ status.HTTP_401_UNAUTHORIZED,
971
+ api.Error(
972
+ type="request",
973
+ code=InternalErrorCode.NOT_ALLOWED,
974
+ message="You don't have permission to this action [41]",
975
+ )
976
+ )
977
+
978
+ attachments = {}
979
+ entry_path = (
980
+ settings.spaces_folder
981
+ / f"{space_name}/{subpath}/.dm/{shortname}"
982
+ )
983
+ if retrieve_attachments:
984
+ attachments = await db.get_entry_attachments(
985
+ subpath=subpath,
986
+ attachments_path=entry_path,
987
+ retrieve_json_payload=retrieve_json_payload,
988
+ filter_types=filter_attachments_types,
989
+ )
990
+
991
+ if (
992
+ not retrieve_json_payload
993
+ or not meta.payload
994
+ or not meta.payload.body
995
+ or not isinstance(meta.payload.body, str)
996
+ or meta.payload.content_type != ContentType.json
997
+ ):
998
+ # TODO
999
+ # include locked before returning the dictionary
1000
+ return {**meta.model_dump(exclude_none=True), "attachments": attachments}
1001
+
1002
+ payload_body = await db.load_resource_payload(
1003
+ space_name=space_name,
1004
+ subpath=subpath,
1005
+ filename=meta.payload.body,
1006
+ class_type=resource_class,
1007
+ )
1008
+
1009
+ if meta.payload and meta.payload.schema_shortname and validate_schema and payload_body:
1010
+ await db.validate_payload_with_schema(
1011
+ payload_data=payload_body,
1012
+ space_name=space_name,
1013
+ schema_shortname=meta.payload.schema_shortname,
1014
+ )
1015
+
1016
+ if payload_body is not None:
1017
+ meta.payload.body = payload_body
1018
+ await plugin_manager.after_action(
1019
+ core.Event(
1020
+ space_name=space_name,
1021
+ subpath=subpath,
1022
+ shortname=shortname,
1023
+ action_type=core.ActionType.view,
1024
+ resource_type=resource_type,
1025
+ user_shortname=logged_in_user,
1026
+ )
1027
+ )
1028
+ # TODO
1029
+ # include locked before returning the dictionary
1030
+ return {**meta.model_dump(exclude_none=True), "attachments": attachments}
1031
+
1032
+
1033
+ @router.get("/byuuid/{uuid}", response_model_exclude_none=True)
1034
+ async def get_entry_by_uuid(
1035
+ uuid: str,
1036
+ retrieve_json_payload: bool = False,
1037
+ retrieve_attachments: bool = False,
1038
+ retrieve_lock_status: bool = False,
1039
+ logged_in_user=Depends(JWTBearer()),
1040
+ ):
1041
+ return await db.get_entry_by_var(
1042
+ "uuid",
1043
+ uuid,
1044
+ logged_in_user,
1045
+ retrieve_json_payload,
1046
+ retrieve_attachments,
1047
+ retrieve_lock_status,
1048
+ )
1049
+
1050
+
1051
+ @router.get("/byslug/{slug}", response_model_exclude_none=True)
1052
+ async def get_entry_by_slug(
1053
+ slug: str,
1054
+ retrieve_json_payload: bool = False,
1055
+ retrieve_attachments: bool = False,
1056
+ retrieve_lock_status: bool = False,
1057
+ logged_in_user=Depends(JWTBearer()),
1058
+ ):
1059
+ return await db.get_entry_by_var(
1060
+ "slug",
1061
+ slug,
1062
+ logged_in_user,
1063
+ retrieve_json_payload,
1064
+ retrieve_attachments,
1065
+ retrieve_lock_status,
1066
+ )
1067
+
1068
+
1069
+ @router.get("/health/{health_type}/{space_name}", response_model_exclude_none=True)
1070
+ async def get_space_report(
1071
+ space_name: str = Path(..., pattern=regex.SPACENAME, examples=["data"]),
1072
+ health_type: str = Path(..., examples=["soft", "hard"]),
1073
+ logged_in_user=Depends(JWTBearer()),
1074
+ ):
1075
+ if logged_in_user != "dmart":
1076
+ raise api.Exception(
1077
+ status_code=status.HTTP_401_UNAUTHORIZED,
1078
+ error=api.Error(
1079
+ type="access",
1080
+ code=InternalErrorCode.NOT_ALLOWED,
1081
+ message="You don't have permission to this action [23]"
1082
+ ),
1083
+ )
1084
+
1085
+ await is_space_exist(space_name)
1086
+
1087
+ if health_type not in ["soft", "hard"]:
1088
+ raise api.Exception(
1089
+ status.HTTP_400_BAD_REQUEST,
1090
+ error=api.Error(
1091
+ type="media",
1092
+ code=InternalErrorCode.INVALID_HEALTH_CHECK,
1093
+ message="Invalid health check type"
1094
+ ),
1095
+ )
1096
+
1097
+ os.system(
1098
+ f"./health_check.py -t {health_type} -s {space_name} &")
1099
+ return api.Response(
1100
+ status=api.Status.success,
1101
+ )
1102
+
1103
+
1104
+ @router.put("/lock/{resource_type}/{space_name}/{subpath:path}/{shortname}")
1105
+ async def lock_entry(
1106
+ space_name: str = Path(..., pattern=regex.SPACENAME),
1107
+ subpath: str = Path(..., pattern=regex.SUBPATH),
1108
+ shortname: str = Path(..., pattern=regex.SHORTNAME),
1109
+ resource_type: ResourceType | None = ResourceType.ticket,
1110
+ logged_in_user=Depends(JWTBearer()),
1111
+ ):
1112
+ await plugin_manager.before_action(
1113
+ core.Event(
1114
+ space_name=space_name,
1115
+ subpath=subpath,
1116
+ shortname=shortname,
1117
+ action_type=core.ActionType.lock,
1118
+ user_shortname=logged_in_user,
1119
+ )
1120
+ )
1121
+
1122
+ if resource_type == ResourceType.ticket:
1123
+ cls = getattr(sys.modules["models.core"], camel_case(resource_type))
1124
+
1125
+ mm = await db.load(
1126
+ space_name=space_name,
1127
+ subpath=subpath,
1128
+ shortname=shortname,
1129
+ class_type=cls,
1130
+ user_shortname=logged_in_user,
1131
+ )
1132
+
1133
+ meta = mm
1134
+ meta.collaborators = meta.collaborators if meta.collaborators else {}
1135
+ if meta.collaborators.get("processed_by") != logged_in_user:
1136
+ meta.collaborators["processed_by"] = logged_in_user
1137
+ request = api.Request(
1138
+ space_name=space_name,
1139
+ request_type=api.RequestType.update,
1140
+ records=[
1141
+ core.Record(
1142
+ resource_type=resource_type,
1143
+ subpath=subpath,
1144
+ shortname=shortname,
1145
+ attributes={
1146
+ "is_active": True,
1147
+ "collaborators": meta.collaborators,
1148
+ },
1149
+ )
1150
+ ],
1151
+ )
1152
+ await serve_request(request=request, owner_shortname=logged_in_user)
1153
+
1154
+ # if lock file is doesn't exist
1155
+ # elif lock file exit but lock_period expired
1156
+ # elif lock file exist and lock_period isn't expired but the owner want to extend the lock
1157
+
1158
+ lock_type = await db.lock_handler(
1159
+ space_name,
1160
+ subpath,
1161
+ shortname,
1162
+ logged_in_user,
1163
+ LockAction.lock
1164
+ )
1165
+
1166
+ await db.store_entry_diff(
1167
+ space_name,
1168
+ "/" + subpath,
1169
+ shortname,
1170
+ logged_in_user,
1171
+ {},
1172
+ {"lock_type": lock_type},
1173
+ ["lock_type"],
1174
+ core.Content,
1175
+ )
1176
+
1177
+ await plugin_manager.after_action(
1178
+ core.Event(
1179
+ space_name=space_name,
1180
+ subpath=subpath,
1181
+ shortname=shortname,
1182
+ resource_type=ResourceType.ticket,
1183
+ action_type=core.ActionType.lock,
1184
+ user_shortname=logged_in_user,
1185
+ )
1186
+ )
1187
+
1188
+ return api.Response(
1189
+ status=api.Status.success,
1190
+ attributes={
1191
+ "message": f"Successfully locked the entry for {settings.lock_period} seconds",
1192
+ "lock_period": settings.lock_period,
1193
+ },
1194
+ )
1195
+
1196
+
1197
+ @router.delete("/lock/{space_name}/{subpath:path}/{shortname}")
1198
+ async def cancel_lock(
1199
+ space_name: str = Path(..., pattern=regex.SPACENAME),
1200
+ subpath: str = Path(..., pattern=regex.SUBPATH),
1201
+ shortname: str = Path(..., pattern=regex.SHORTNAME),
1202
+ logged_in_user=Depends(JWTBearer()),
1203
+ ):
1204
+ lock_payload = await db.lock_handler(space_name, subpath, shortname, logged_in_user, LockAction.fetch)
1205
+
1206
+ if not lock_payload or lock_payload["owner_shortname"] != logged_in_user:
1207
+ raise api.Exception(
1208
+ status_code=status.HTTP_403_FORBIDDEN,
1209
+ error=api.Error(
1210
+ type="lock",
1211
+ code=InternalErrorCode.LOCK_UNAVAILABLE,
1212
+ message="Lock does not exist or you have no access",
1213
+ ),
1214
+ )
1215
+
1216
+ await plugin_manager.before_action(
1217
+ core.Event(
1218
+ space_name=space_name,
1219
+ subpath=subpath,
1220
+ shortname=shortname,
1221
+ action_type=core.ActionType.unlock,
1222
+ user_shortname=logged_in_user,
1223
+ )
1224
+ )
1225
+
1226
+ await db.lock_handler(
1227
+ space_name,
1228
+ subpath,
1229
+ shortname,
1230
+ logged_in_user,
1231
+ LockAction.unlock
1232
+ )
1233
+
1234
+ await db.store_entry_diff(
1235
+ space_name,
1236
+ "/" + subpath,
1237
+ shortname,
1238
+ logged_in_user,
1239
+ {},
1240
+ {"lock_type": LockAction.cancel},
1241
+ ["lock_type"],
1242
+ core.Content,
1243
+ )
1244
+
1245
+ await plugin_manager.after_action(
1246
+ core.Event(
1247
+ space_name=space_name,
1248
+ subpath=subpath,
1249
+ shortname=shortname,
1250
+ resource_type=ResourceType.ticket,
1251
+ action_type=core.ActionType.unlock,
1252
+ user_shortname=logged_in_user,
1253
+ )
1254
+ )
1255
+
1256
+ return api.Response(
1257
+ status=api.Status.success,
1258
+ attributes={"message": "Entry unlocked successfully"},
1259
+ )
1260
+
1261
+
1262
+ @router.get("/reload-security-data")
1263
+ async def reload_security_data(_=Depends(JWTBearer())):
1264
+ if settings.active_data_db == "file":
1265
+ await access_control.load_permissions_and_roles()
1266
+
1267
+ return api.Response(status=api.Status.success)
1268
+
1269
+
1270
+ @router.post("/excute/{task_type}/{space_name}")
1271
+ async def execute(
1272
+ space_name: str,
1273
+ task_type: TaskType,
1274
+ record: core.Record,
1275
+ logged_in_user=Depends(JWTBearer()),
1276
+ ):
1277
+ task_type = task_type
1278
+ meta = await db.load(
1279
+ space_name=space_name,
1280
+ subpath=record.subpath,
1281
+ shortname=record.shortname,
1282
+ class_type=core.Content,
1283
+ user_shortname=logged_in_user,
1284
+ )
1285
+
1286
+ if (
1287
+ meta.payload is None
1288
+ or not isinstance(meta.payload.body, str)
1289
+ or not str(meta.payload.body).endswith(".json")
1290
+ ):
1291
+ raise api.Exception(
1292
+ status.HTTP_400_BAD_REQUEST,
1293
+ error=api.Error(
1294
+ type="media", code=InternalErrorCode.OBJECT_NOT_FOUND, message="Request object is not available"
1295
+ ),
1296
+ )
1297
+
1298
+ mydict = await db.load_resource_payload(
1299
+ space_name=space_name,
1300
+ subpath=record.subpath,
1301
+ filename=str(meta.payload.body),
1302
+ class_type=core.Content,
1303
+ )
1304
+
1305
+ query_dict = mydict if mydict else {}
1306
+
1307
+ if meta.payload.schema_shortname == "report":
1308
+ query_dict = query_dict["query"]
1309
+ else:
1310
+ query_dict["subpath"] = query_dict["query_subpath"]
1311
+ query_dict.pop("query_subpath")
1312
+
1313
+ for param, value in record.attributes.items():
1314
+ query_dict["search"] = query_dict["search"].replace(
1315
+ f"${param}", str(value))
1316
+
1317
+ query_dict["search"] = res_sub(
1318
+ r"@\w*\:({|\()?\$\w*(}|\))?", "", query_dict["search"]
1319
+ )
1320
+
1321
+ if "offset" in record.attributes:
1322
+ query_dict["offset"] = record.attributes["offset"]
1323
+
1324
+ if "limit" in record.attributes:
1325
+ query_dict["limit"] = record.attributes["limit"]
1326
+
1327
+ if "from_date" in record.attributes:
1328
+ query_dict["from_date"] = record.attributes["from_date"]
1329
+
1330
+ if "to_date" in record.attributes:
1331
+ query_dict["to_date"] = record.attributes["to_date"]
1332
+
1333
+ return await query_entries(
1334
+ query=api.Query(**query_dict), user_shortname=logged_in_user
1335
+ )
1336
+
1337
+
1338
+ @router.get(
1339
+ "/s/{token}",
1340
+ response_model_exclude_none=True,
1341
+ )
1342
+ async def shoting_url(
1343
+ token: str,
1344
+ ):
1345
+ if url := await db.get_url_shortner(token):
1346
+ return RedirectResponse(url=url)
1347
+
1348
+ return RedirectResponse(url="/web")
1349
+
1350
+
1351
+ @router.post(
1352
+ "/apply-alteration/{space_name}/{alteration_name}", response_model_exclude_none=True
1353
+ )
1354
+ async def apply_alteration(
1355
+ space_name: str,
1356
+ alteration_name: str,
1357
+ on_entry: core.Record,
1358
+ logged_in_user=Depends(JWTBearer()),
1359
+ ):
1360
+ alteration_meta = await db.load(
1361
+ space_name=space_name,
1362
+ subpath=f"{on_entry.subpath}/{on_entry.shortname}",
1363
+ shortname=alteration_name,
1364
+ class_type=core.Alteration,
1365
+ user_shortname=logged_in_user,
1366
+ )
1367
+ entry_meta: core.Meta = await db.load(
1368
+ space_name=space_name,
1369
+ subpath=f"{on_entry.subpath}",
1370
+ shortname=on_entry.shortname,
1371
+ class_type=getattr(
1372
+ sys.modules["models.core"], camel_case(on_entry.resource_type)
1373
+ ),
1374
+ user_shortname=logged_in_user,
1375
+ )
1376
+
1377
+ record: core.Record = entry_meta.to_record(
1378
+ on_entry.subpath, on_entry.shortname, []
1379
+ )
1380
+ record.attributes["payload"] = record.attributes["payload"].__dict__
1381
+ record.attributes["payload"]["body"] = alteration_meta.requested_update
1382
+
1383
+ response = await serve_request(
1384
+ request=api.Request(
1385
+ space_name=space_name, request_type=RequestType.update, records=[
1386
+ record]
1387
+ ),
1388
+ owner_shortname=logged_in_user,
1389
+ )
1390
+
1391
+ await db.delete(
1392
+ space_name=space_name,
1393
+ subpath=f"{on_entry.subpath}/{on_entry.shortname}",
1394
+ meta=alteration_meta,
1395
+ user_shortname=logged_in_user,
1396
+ retrieve_lock_status=on_entry.retrieve_lock_status,
1397
+ )
1398
+ return response
1399
+
1400
+ """
1401
+ @router.post("/data-asset")
1402
+ async def data_asset(
1403
+ query: api.DataAssetQuery,
1404
+ _=Depends(JWTBearer()),
1405
+ ):
1406
+ try:
1407
+ duckdb = __import__("duckdb")
1408
+ except ModuleNotFoundError:
1409
+ raise api.Exception(
1410
+ status.HTTP_400_BAD_REQUEST,
1411
+ api.Error(
1412
+ type="request",
1413
+ code=InternalErrorCode.NOT_ALLOWED,
1414
+ message="duckdb is not installed!",
1415
+ ),
1416
+ )
1417
+
1418
+ attachments: dict[str, list[core.Record]] = await db.get_entry_attachments(
1419
+ subpath=f"{query.subpath}/{query.shortname}",
1420
+ attachments_path=(
1421
+ settings.spaces_folder
1422
+ / f"{query.space_name}/{query.subpath}/.dm/{query.shortname}"
1423
+ ),
1424
+ filter_types=[query.data_asset_type],
1425
+ filter_shortnames=query.filter_data_assets
1426
+ )
1427
+ files_paths: list[FilePath] = await data_asset_attachments_handler(query, attachments)
1428
+ if not files_paths:
1429
+ raise api.Exception(
1430
+ status.HTTP_400_BAD_REQUEST,
1431
+ api.Error(
1432
+ type="request",
1433
+ code=InternalErrorCode.OBJECT_NOT_FOUND,
1434
+ message="No data asset attachments found for this entry",
1435
+ ),
1436
+ )
1437
+
1438
+ if query.data_asset_type in [DataAssetType.sqlite, DataAssetType.duckdb]:
1439
+ conn: duckdb.DuckDBPyConnection = duckdb.connect(str(files_paths[0]))
1440
+ else:
1441
+ conn = duckdb.connect(":default:")
1442
+ await data_asset_handler(conn, query, files_paths, attachments)
1443
+
1444
+ data: duckdb.DuckDBPyRelation = conn.sql(query=query.query_string)
1445
+
1446
+ temp_file = f"temp_file_from_duckdb_{int(round(time() * 1000))}.csv"
1447
+ data.write_csv(file_name=temp_file)
1448
+ data_objects: list[dict[str, Any]] = await csv_file_to_json(FilePath(temp_file))
1449
+ os.remove(temp_file)
1450
+
1451
+ return data_objects
1452
+
1453
+
1454
+ @router.get("/data-asset")
1455
+ async def data_asset_single(
1456
+ resource_type: ResourceType,
1457
+ space_name: str = Path(..., pattern=regex.SPACENAME, examples=["data"]),
1458
+ subpath: str = Path(..., pattern=regex.SUBPATH, examples=["/content"]),
1459
+ shortname: str = Path(..., pattern=regex.SHORTNAME,
1460
+ examples=["unique_shortname"]),
1461
+ schema_shortname: str | None = None,
1462
+ ext: str = Path(..., pattern=regex.EXT, examples=["png"]),
1463
+ logged_in_user=Depends(JWTBearer()),
1464
+ ) -> StreamingResponse:
1465
+ await plugin_manager.before_action(
1466
+ core.Event(
1467
+ space_name=space_name,
1468
+ subpath=subpath,
1469
+ shortname=shortname,
1470
+ action_type=core.ActionType.view,
1471
+ resource_type=resource_type,
1472
+ user_shortname=logged_in_user,
1473
+ )
1474
+ )
1475
+
1476
+ cls = getattr(sys.modules["models.core"], camel_case(resource_type))
1477
+ meta: core.Meta = await db.load(
1478
+ space_name=space_name,
1479
+ subpath=subpath,
1480
+ shortname=shortname,
1481
+ class_type=cls,
1482
+ user_shortname=logged_in_user,
1483
+ schema_shortname=schema_shortname,
1484
+ )
1485
+ if (
1486
+ meta.payload is None
1487
+ or meta.payload.body is None
1488
+ or meta.payload.body != f"{shortname}.{ext}"
1489
+ ):
1490
+ raise api.Exception(
1491
+ status.HTTP_400_BAD_REQUEST,
1492
+ error=api.Error(
1493
+ type="media", code=InternalErrorCode.OBJECT_NOT_FOUND, message="Request object is not available"
1494
+ ),
1495
+ )
1496
+
1497
+ if not await access_control.check_access(
1498
+ user_shortname=logged_in_user,
1499
+ space_name=space_name,
1500
+ subpath=subpath,
1501
+ resource_type=resource_type,
1502
+ action_type=core.ActionType.view,
1503
+ resource_is_active=meta.is_active,
1504
+ resource_owner_shortname=meta.owner_shortname,
1505
+ resource_owner_group=meta.owner_group_shortname,
1506
+ entry_shortname=meta.shortname
1507
+ ):
1508
+ raise api.Exception(
1509
+ status.HTTP_401_UNAUTHORIZED,
1510
+ api.Error(
1511
+ type="request",
1512
+ code=InternalErrorCode.NOT_ALLOWED,
1513
+ message="You don't have permission to this action [9]",
1514
+ ),
1515
+ )
1516
+
1517
+ payload_path = db.payload_path(
1518
+ space_name=space_name,
1519
+ subpath=subpath,
1520
+ class_type=cls,
1521
+ schema_shortname=schema_shortname,
1522
+ )
1523
+ await plugin_manager.after_action(
1524
+ core.Event(
1525
+ space_name=space_name,
1526
+ subpath=subpath,
1527
+ shortname=shortname,
1528
+ action_type=core.ActionType.view,
1529
+ resource_type=resource_type,
1530
+ user_shortname=logged_in_user,
1531
+ )
1532
+ )
1533
+
1534
+ with open(payload_path / str(meta.payload.body), "r") as csv_file:
1535
+ media = "text/csv" if resource_type == ResourceType.csv else "text/plain"
1536
+ response = StreamingResponse(iter(csv_file.read()), media_type=media)
1537
+ response.headers["Content-Disposition"] = "attachment; filename=data.csv"
1538
+
1539
+ return response
1540
+
1541
+ """