dmart 1.4.17__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 (289) hide show
  1. alembic.ini +117 -0
  2. api/__init__.py +0 -0
  3. api/info/__init__.py +0 -0
  4. api/info/router.py +109 -0
  5. api/managed/__init__.py +0 -0
  6. api/managed/router.py +1541 -0
  7. api/managed/utils.py +1879 -0
  8. api/public/__init__.py +0 -0
  9. api/public/router.py +758 -0
  10. api/qr/__init__.py +0 -0
  11. api/qr/router.py +108 -0
  12. api/user/__init__.py +0 -0
  13. api/user/model/__init__.py +0 -0
  14. api/user/model/errors.py +14 -0
  15. api/user/model/requests.py +165 -0
  16. api/user/model/responses.py +11 -0
  17. api/user/router.py +1413 -0
  18. api/user/service.py +270 -0
  19. bundler.py +55 -0
  20. config/__init__.py +0 -0
  21. config/channels.json +11 -0
  22. config/notification.json +17 -0
  23. cxb/__init__.py +0 -0
  24. cxb/client/__init__.py +0 -0
  25. cxb/client/assets/@codemirror-Rn7_6DkE.js +10 -0
  26. cxb/client/assets/@edraj-CS4NwVbD.js +1 -0
  27. cxb/client/assets/@floating-ui-BwwcF-xh.js +1 -0
  28. cxb/client/assets/@formatjs-yKEsAtjs.js +1 -0
  29. cxb/client/assets/@fortawesome-DRW1UCdr.js +9 -0
  30. cxb/client/assets/@jsonquerylang-laKNoFFq.js +12 -0
  31. cxb/client/assets/@lezer-za4Q-8Ew.js +1 -0
  32. cxb/client/assets/@marijn-DXwl3gUT.js +1 -0
  33. cxb/client/assets/@popperjs-l0sNRNKZ.js +1 -0
  34. cxb/client/assets/@replit--ERk53eB.js +1 -0
  35. cxb/client/assets/@roxi-CGMFK4i8.js +6 -0
  36. cxb/client/assets/@typewriter-cCzskkIv.js +17 -0
  37. cxb/client/assets/@zerodevx-BlBZjKxu.js +1 -0
  38. cxb/client/assets/@zerodevx-CVEpe6WZ.css +1 -0
  39. cxb/client/assets/BreadCrumbLite-DAhOx38v.js +1 -0
  40. cxb/client/assets/EntryRenderer-25YDhRen.js +32 -0
  41. cxb/client/assets/EntryRenderer-DXytdFp9.css +1 -0
  42. cxb/client/assets/ListView-BpAycA2h.js +16 -0
  43. cxb/client/assets/ListView-U8of-_c-.css +1 -0
  44. cxb/client/assets/Prism--hMplq-p.js +3 -0
  45. cxb/client/assets/Prism-Uh6uStUw.css +1 -0
  46. cxb/client/assets/Table2Cols-BsbwicQm.js +1 -0
  47. cxb/client/assets/_..-BvT6vdHa.css +1 -0
  48. cxb/client/assets/_...404_-fuLH_rX9.js +2 -0
  49. cxb/client/assets/_...fallback_-Ba_NLmAE.js +1 -0
  50. cxb/client/assets/_module-Bfk8MiCs.js +3 -0
  51. cxb/client/assets/_module-CEW0D5oI.js +4 -0
  52. cxb/client/assets/_module-Dgq0ZVtz.js +1 -0
  53. cxb/client/assets/ajv-Cpj98o6Y.js +1 -0
  54. cxb/client/assets/axios-CG2WSiiR.js +6 -0
  55. cxb/client/assets/clsx-B-dksMZM.js +1 -0
  56. cxb/client/assets/codemirror-wrapped-line-indent-DPhKvljI.js +1 -0
  57. cxb/client/assets/compare-C3AjiGFR.js +1 -0
  58. cxb/client/assets/compute-scroll-into-view-Bl8rNFhg.js +1 -0
  59. cxb/client/assets/consolite-DlCuI0F9.js +1 -0
  60. cxb/client/assets/crelt-C8TCjufn.js +1 -0
  61. cxb/client/assets/date-fns-l0sNRNKZ.js +1 -0
  62. cxb/client/assets/deepmerge-rn4rBaHU.js +1 -0
  63. cxb/client/assets/dmart_services-AL6-IdDE.js +1 -0
  64. cxb/client/assets/downloadFile-D08i0YDh.js +1 -0
  65. cxb/client/assets/easy-signal-BiPFIK3O.js +1 -0
  66. cxb/client/assets/esm-env-rsSWfq8L.js +1 -0
  67. cxb/client/assets/export-OF_rTiXu.js +1 -0
  68. cxb/client/assets/fast-deep-equal-l0sNRNKZ.js +1 -0
  69. cxb/client/assets/fast-diff-C-IidNf4.js +1 -0
  70. cxb/client/assets/fast-uri-l0sNRNKZ.js +1 -0
  71. cxb/client/assets/flowbite-svelte-BLvjb-sa.js +1 -0
  72. cxb/client/assets/flowbite-svelte-CD54FDqW.css +1 -0
  73. cxb/client/assets/flowbite-svelte-icons-BI8GVhw_.js +1 -0
  74. cxb/client/assets/github-slugger-CQ4oX9Ud.js +1 -0
  75. cxb/client/assets/global-igKv-1g9.js +1 -0
  76. cxb/client/assets/hookar-BMRD9G9H.js +1 -0
  77. cxb/client/assets/immutable-json-patch-DtRO2E_S.js +1 -0
  78. cxb/client/assets/import-1vE3gBat.js +1 -0
  79. cxb/client/assets/index-B-eTh-ZX.js +1 -0
  80. cxb/client/assets/index-BVyxzKtH.js +1 -0
  81. cxb/client/assets/index-BdeNM69f.js +1 -0
  82. cxb/client/assets/index-C6cPO4op.js +1 -0
  83. cxb/client/assets/index-CC-A1ipE.js +1 -0
  84. cxb/client/assets/index-CTxJ-lDp.js +1 -0
  85. cxb/client/assets/index-Cd-F5j_k.js +1 -0
  86. cxb/client/assets/index-D742rwaM.js +1 -0
  87. cxb/client/assets/index-DTfhnhwd.js +1 -0
  88. cxb/client/assets/index-DdXRK7n9.js +2 -0
  89. cxb/client/assets/index-DtiCmB4o.js +1 -0
  90. cxb/client/assets/index-NBrXBlLA.css +2 -0
  91. cxb/client/assets/index-ac-Buu_H.js +4 -0
  92. cxb/client/assets/index-iYkH7C67.js +1 -0
  93. cxb/client/assets/info-B986lRiM.js +1 -0
  94. cxb/client/assets/intl-messageformat-Dc5UU-HB.js +3 -0
  95. cxb/client/assets/jmespath-l0sNRNKZ.js +1 -0
  96. cxb/client/assets/json-schema-traverse-l0sNRNKZ.js +1 -0
  97. cxb/client/assets/json-source-map-DRgZidqy.js +5 -0
  98. cxb/client/assets/jsonpath-plus-l0sNRNKZ.js +1 -0
  99. cxb/client/assets/jsonrepair-B30Dx381.js +8 -0
  100. cxb/client/assets/lodash-es-DZVAA2ox.js +1 -0
  101. cxb/client/assets/marked-DKjyhwJX.js +56 -0
  102. cxb/client/assets/marked-gfm-heading-id-U5zO829x.js +2 -0
  103. cxb/client/assets/marked-mangle-CDMeiHC6.js +1 -0
  104. cxb/client/assets/memoize-one-BdPwpGay.js +1 -0
  105. cxb/client/assets/natural-compare-lite-Bg2Xcf-o.js +7 -0
  106. cxb/client/assets/pagination-svelte-D5CyoiE_.js +13 -0
  107. cxb/client/assets/pagination-svelte-v10nAbbM.css +1 -0
  108. cxb/client/assets/plantuml-encoder-C47mzt9T.js +1 -0
  109. cxb/client/assets/prismjs-DTUiLGJu.js +9 -0
  110. cxb/client/assets/profile-BUf-tKMe.js +1 -0
  111. cxb/client/assets/query-CNmXTsgf.js +1 -0
  112. cxb/client/assets/queryHelpers-C9iBWwqe.js +1 -0
  113. cxb/client/assets/scroll-into-view-if-needed-KR58zyjF.js +1 -0
  114. cxb/client/assets/spaces-0oyGvpii.js +1 -0
  115. cxb/client/assets/style-mod-Bs6eFhZE.js +3 -0
  116. cxb/client/assets/svelte-B2XmcTi_.js +4 -0
  117. cxb/client/assets/svelte-awesome-COLlx0DN.css +1 -0
  118. cxb/client/assets/svelte-awesome-DhnMA6Q_.js +1 -0
  119. cxb/client/assets/svelte-datatables-net-CY7LBj6I.js +1 -0
  120. cxb/client/assets/svelte-floating-ui-BlS3sOAQ.js +1 -0
  121. cxb/client/assets/svelte-i18n-CT2KkQaN.js +3 -0
  122. cxb/client/assets/svelte-jsoneditor-BzfX6Usi.css +1 -0
  123. cxb/client/assets/svelte-jsoneditor-CUGSvWId.js +25 -0
  124. cxb/client/assets/svelte-select-CegQKzqH.css +1 -0
  125. cxb/client/assets/svelte-select-CjHAt_85.js +6 -0
  126. cxb/client/assets/tailwind-merge-CJvxXMcu.js +1 -0
  127. cxb/client/assets/tailwind-variants-Cj20BoQ3.js +1 -0
  128. cxb/client/assets/toast-B9WDyfyI.js +1 -0
  129. cxb/client/assets/tslib-pJfR_DrR.js +1 -0
  130. cxb/client/assets/typewriter-editor-DkTVIJdm.js +25 -0
  131. cxb/client/assets/user-DeK_NB5v.js +1 -0
  132. cxb/client/assets/vanilla-picker-l5rcX3cq.js +8 -0
  133. cxb/client/assets/w3c-keyname-Vcq4gwWv.js +1 -0
  134. cxb/client/config.json +11 -0
  135. cxb/client/config.sample.json +11 -0
  136. cxb/client/favicon.ico +0 -0
  137. cxb/client/favicon.png +0 -0
  138. cxb/client/index.html +28 -0
  139. data_adapters/__init__.py +0 -0
  140. data_adapters/adapter.py +16 -0
  141. data_adapters/base_data_adapter.py +467 -0
  142. data_adapters/file/__init__.py +0 -0
  143. data_adapters/file/adapter.py +2043 -0
  144. data_adapters/file/adapter_helpers.py +1013 -0
  145. data_adapters/file/archive.py +150 -0
  146. data_adapters/file/create_index.py +331 -0
  147. data_adapters/file/create_users_folders.py +52 -0
  148. data_adapters/file/custom_validations.py +68 -0
  149. data_adapters/file/drop_index.py +40 -0
  150. data_adapters/file/health_check.py +560 -0
  151. data_adapters/file/redis_services.py +1110 -0
  152. data_adapters/helpers.py +27 -0
  153. data_adapters/sql/__init__.py +0 -0
  154. data_adapters/sql/adapter.py +3218 -0
  155. data_adapters/sql/adapter_helpers.py +491 -0
  156. data_adapters/sql/create_tables.py +451 -0
  157. data_adapters/sql/create_users_folders.py +53 -0
  158. data_adapters/sql/db_to_json_migration.py +485 -0
  159. data_adapters/sql/health_check_sql.py +232 -0
  160. data_adapters/sql/json_to_db_migration.py +454 -0
  161. data_adapters/sql/update_query_policies.py +101 -0
  162. data_generator.py +81 -0
  163. dmart-1.4.17.dist-info/METADATA +65 -0
  164. dmart-1.4.17.dist-info/RECORD +289 -0
  165. dmart-1.4.17.dist-info/WHEEL +5 -0
  166. dmart-1.4.17.dist-info/entry_points.txt +2 -0
  167. dmart-1.4.17.dist-info/top_level.txt +24 -0
  168. dmart.py +623 -0
  169. dmart_migrations/README +1 -0
  170. dmart_migrations/__init__.py +0 -0
  171. dmart_migrations/__pycache__/__init__.cpython-314.pyc +0 -0
  172. dmart_migrations/__pycache__/env.cpython-314.pyc +0 -0
  173. dmart_migrations/env.py +100 -0
  174. dmart_migrations/notes.txt +11 -0
  175. dmart_migrations/script.py.mako +28 -0
  176. dmart_migrations/scripts/__init__.py +0 -0
  177. dmart_migrations/scripts/calculate_checksums.py +77 -0
  178. dmart_migrations/scripts/migration_f7a4949eed19.py +28 -0
  179. dmart_migrations/versions/0f3d2b1a7c21_add_authz_materialized_views.py +87 -0
  180. dmart_migrations/versions/10d2041b94d4_last_checksum_history.py +62 -0
  181. dmart_migrations/versions/1cf4e1ee3cb8_ext_permission_with_filter_fields_values.py +33 -0
  182. dmart_migrations/versions/26bfe19b49d4_rm_failedloginattempts.py +42 -0
  183. dmart_migrations/versions/3c8bca2219cc_add_otp_table.py +38 -0
  184. dmart_migrations/versions/6675fd9dfe42_remove_unique_from_sessions_table.py +36 -0
  185. dmart_migrations/versions/71bc1df82e6a_adding_user_last_login_at.py +43 -0
  186. dmart_migrations/versions/74288ccbd3b5_initial.py +264 -0
  187. dmart_migrations/versions/7520a89a8467_rm_activesession_table.py +39 -0
  188. dmart_migrations/versions/848b623755a4_make_created_nd_updated_at_required.py +138 -0
  189. dmart_migrations/versions/8640dcbebf85_add_notes_to_users.py +32 -0
  190. dmart_migrations/versions/91c94250232a_adding_fk_on_owner_shortname.py +104 -0
  191. dmart_migrations/versions/98ecd6f56f9a_ext_meta_with_owner_group_shortname.py +66 -0
  192. dmart_migrations/versions/9aae9138c4ef_indexing_created_at_updated_at.py +80 -0
  193. dmart_migrations/versions/__init__.py +0 -0
  194. dmart_migrations/versions/__pycache__/0f3d2b1a7c21_add_authz_materialized_views.cpython-314.pyc +0 -0
  195. dmart_migrations/versions/__pycache__/10d2041b94d4_last_checksum_history.cpython-314.pyc +0 -0
  196. dmart_migrations/versions/__pycache__/1cf4e1ee3cb8_ext_permission_with_filter_fields_values.cpython-314.pyc +0 -0
  197. dmart_migrations/versions/__pycache__/26bfe19b49d4_rm_failedloginattempts.cpython-314.pyc +0 -0
  198. dmart_migrations/versions/__pycache__/3c8bca2219cc_add_otp_table.cpython-314.pyc +0 -0
  199. dmart_migrations/versions/__pycache__/6675fd9dfe42_remove_unique_from_sessions_table.cpython-314.pyc +0 -0
  200. dmart_migrations/versions/__pycache__/71bc1df82e6a_adding_user_last_login_at.cpython-314.pyc +0 -0
  201. dmart_migrations/versions/__pycache__/74288ccbd3b5_initial.cpython-314.pyc +0 -0
  202. dmart_migrations/versions/__pycache__/7520a89a8467_rm_activesession_table.cpython-314.pyc +0 -0
  203. dmart_migrations/versions/__pycache__/848b623755a4_make_created_nd_updated_at_required.cpython-314.pyc +0 -0
  204. dmart_migrations/versions/__pycache__/8640dcbebf85_add_notes_to_users.cpython-314.pyc +0 -0
  205. dmart_migrations/versions/__pycache__/91c94250232a_adding_fk_on_owner_shortname.cpython-314.pyc +0 -0
  206. dmart_migrations/versions/__pycache__/98ecd6f56f9a_ext_meta_with_owner_group_shortname.cpython-314.pyc +0 -0
  207. dmart_migrations/versions/__pycache__/9aae9138c4ef_indexing_created_at_updated_at.cpython-314.pyc +0 -0
  208. dmart_migrations/versions/__pycache__/b53f916b3f6d_json_to_jsonb.cpython-314.pyc +0 -0
  209. dmart_migrations/versions/__pycache__/eb5f1ec65156_adding_user_locked_to_device.cpython-314.pyc +0 -0
  210. dmart_migrations/versions/__pycache__/f7a4949eed19_adding_query_policies_to_meta.cpython-314.pyc +0 -0
  211. dmart_migrations/versions/b53f916b3f6d_json_to_jsonb.py +492 -0
  212. dmart_migrations/versions/eb5f1ec65156_adding_user_locked_to_device.py +36 -0
  213. dmart_migrations/versions/f7a4949eed19_adding_query_policies_to_meta.py +60 -0
  214. get_settings.py +7 -0
  215. info.json +1 -0
  216. languages/__init__.py +0 -0
  217. languages/arabic.json +15 -0
  218. languages/english.json +16 -0
  219. languages/kurdish.json +14 -0
  220. languages/loader.py +12 -0
  221. main.py +560 -0
  222. migrate.py +24 -0
  223. models/__init__.py +0 -0
  224. models/api.py +203 -0
  225. models/core.py +597 -0
  226. models/enums.py +255 -0
  227. password_gen.py +8 -0
  228. plugins/__init__.py +0 -0
  229. plugins/action_log/__init__.py +0 -0
  230. plugins/action_log/plugin.py +121 -0
  231. plugins/admin_notification_sender/__init__.py +0 -0
  232. plugins/admin_notification_sender/plugin.py +124 -0
  233. plugins/ldap_manager/__init__.py +0 -0
  234. plugins/ldap_manager/plugin.py +100 -0
  235. plugins/local_notification/__init__.py +0 -0
  236. plugins/local_notification/plugin.py +123 -0
  237. plugins/realtime_updates_notifier/__init__.py +0 -0
  238. plugins/realtime_updates_notifier/plugin.py +58 -0
  239. plugins/redis_db_update/__init__.py +0 -0
  240. plugins/redis_db_update/plugin.py +188 -0
  241. plugins/resource_folders_creation/__init__.py +0 -0
  242. plugins/resource_folders_creation/plugin.py +81 -0
  243. plugins/system_notification_sender/__init__.py +0 -0
  244. plugins/system_notification_sender/plugin.py +188 -0
  245. plugins/update_access_controls/__init__.py +0 -0
  246. plugins/update_access_controls/plugin.py +9 -0
  247. pytests/__init__.py +0 -0
  248. pytests/api_user_models_erros_test.py +16 -0
  249. pytests/api_user_models_requests_test.py +98 -0
  250. pytests/archive_test.py +72 -0
  251. pytests/base_test.py +300 -0
  252. pytests/get_settings_test.py +14 -0
  253. pytests/json_to_db_migration_test.py +237 -0
  254. pytests/service_test.py +26 -0
  255. pytests/test_info.py +55 -0
  256. pytests/test_status.py +15 -0
  257. run_notification_campaign.py +85 -0
  258. scheduled_notification_handler.py +121 -0
  259. schema_migration.py +208 -0
  260. schema_modulate.py +192 -0
  261. set_admin_passwd.py +55 -0
  262. sync.py +202 -0
  263. utils/__init__.py +0 -0
  264. utils/access_control.py +306 -0
  265. utils/async_request.py +8 -0
  266. utils/exporter.py +309 -0
  267. utils/firebase_notifier.py +57 -0
  268. utils/generate_email.py +37 -0
  269. utils/helpers.py +352 -0
  270. utils/hypercorn_config.py +12 -0
  271. utils/internal_error_code.py +60 -0
  272. utils/jwt.py +124 -0
  273. utils/logger.py +167 -0
  274. utils/middleware.py +99 -0
  275. utils/notification.py +75 -0
  276. utils/password_hashing.py +16 -0
  277. utils/plugin_manager.py +202 -0
  278. utils/query_policies_helper.py +128 -0
  279. utils/regex.py +44 -0
  280. utils/repository.py +529 -0
  281. utils/router_helper.py +19 -0
  282. utils/settings.py +166 -0
  283. utils/sms_notifier.py +21 -0
  284. utils/social_sso.py +67 -0
  285. utils/templates/activation.html.j2 +26 -0
  286. utils/templates/reminder.html.j2 +17 -0
  287. utils/ticket_sys_utils.py +203 -0
  288. utils/web_notifier.py +29 -0
  289. websocket.py +231 -0
models/core.py ADDED
@@ -0,0 +1,597 @@
1
+ import copy
2
+ import json
3
+ from abc import ABC, abstractmethod
4
+ from pydantic import BaseModel, ConfigDict
5
+ from typing import Any, Dict, TypeVar
6
+ from pydantic.types import UUID4 as UUID
7
+ from uuid import uuid4
8
+ from pydantic import Field
9
+ from datetime import datetime
10
+ import sys
11
+ from models.enums import (
12
+ ActionType,
13
+ ContentType,
14
+ Language,
15
+ NotificationPriority,
16
+ NotificationType,
17
+ ResourceType,
18
+ UserType,
19
+ ConditionType,
20
+ PluginType,
21
+ EventListenTime,
22
+ )
23
+ from utils.helpers import camel_case, remove_none_dict, snake_case
24
+ import utils.regex as regex
25
+ from utils.settings import settings
26
+ import utils.password_hashing as password_hashing
27
+ from hashlib import sha1 as hashlib_sha1
28
+
29
+ KeyType = TypeVar('KeyType')
30
+ def deep_update(mapping: Dict[KeyType, Any], *updating_mappings: Dict[KeyType, Any]) -> Dict[KeyType, Any]:
31
+ updated_mapping = mapping.copy()
32
+ for updating_mapping in updating_mappings:
33
+ for k, v in updating_mapping.items():
34
+ if k in updated_mapping and isinstance(updated_mapping[k], dict) and isinstance(v, dict):
35
+ updated_mapping[k] = deep_update(updated_mapping[k], v)
36
+ else:
37
+ updated_mapping[k] = v
38
+ return updated_mapping
39
+
40
+
41
+ # class MoveModel(BaseModel):
42
+ # resource_type: ResourceType
43
+ # src_shortname: str = Field(regex=regex.SHORTNAME)
44
+ # src_subpath: str = Field(regex=regex.SUBPATH)
45
+ # dist_shortname: str = Field(default=None, regex=regex.SHORTNAME)
46
+ # dist_subpath: str = Field(default=None, regex=regex.SUBPATH)
47
+
48
+
49
+ class Resource(BaseModel):
50
+ model_config = ConfigDict(use_enum_values=True, arbitrary_types_allowed=True)
51
+
52
+
53
+ class Payload(Resource):
54
+ content_type: ContentType
55
+ content_sub_type: str | None = (
56
+ None # FIXME change to proper content type static hashmap
57
+ )
58
+ schema_shortname: str | None = None
59
+ client_checksum: str | None = None
60
+ checksum: str | None = None
61
+ body: str | dict[str, Any] | None
62
+
63
+ def __init__(self, **data):
64
+ BaseModel.__init__(self, **data)
65
+
66
+ if not self.checksum and self.body:
67
+ sha1 = hashlib_sha1()
68
+
69
+ if isinstance(self.body, dict):
70
+ sha1.update(json.dumps(self.body).encode('utf-8'))
71
+ else:
72
+ sha1.update(self.body.encode('utf-8'))
73
+
74
+ self.checksum = sha1.hexdigest()
75
+
76
+ def update(
77
+ self, payload: dict, old_body: dict | None = None, replace: bool = False
78
+ ) -> dict | None:
79
+
80
+ if payload.get("body", None) is None:
81
+ return None
82
+
83
+ if isinstance(payload["body"], dict):
84
+ if old_body and not replace:
85
+ separate_payload_body = dict(
86
+ remove_none_dict(
87
+ deep_update(
88
+ old_body,
89
+ payload["body"],
90
+ )
91
+ )
92
+ )
93
+ else:
94
+ separate_payload_body = payload["body"]
95
+
96
+ if "schema_shortname" in payload:
97
+ self.schema_shortname = payload["schema_shortname"]
98
+
99
+ return separate_payload_body
100
+
101
+ else:
102
+ self.body = payload["body"]
103
+ return None
104
+
105
+
106
+ class Record(BaseModel):
107
+ resource_type: ResourceType
108
+ uuid: UUID | None = None
109
+ shortname: str = Field(pattern=regex.SHORTNAME)
110
+ subpath: str = Field(pattern=regex.SUBPATH)
111
+ attributes: dict[str, Any]
112
+ attachments: dict[ResourceType, list[Any]] | None = None
113
+ retrieve_lock_status: bool = False
114
+
115
+ def __init__(self, **data):
116
+ BaseModel.__init__(self, **data)
117
+ if self.subpath != "/":
118
+ self.subpath = self.subpath.strip("/")
119
+
120
+ def to_dict(self):
121
+ return self.model_dump(exclude_none=True, warnings="error")
122
+
123
+ def __eq__(self, other):
124
+ return (
125
+ isinstance(other, Record)
126
+ and self.shortname == other.shortname
127
+ and self.subpath == other.subpath
128
+ )
129
+
130
+ model_config = ConfigDict(
131
+ extra= "forbid",
132
+ json_schema_extra= { "examples": [
133
+ {
134
+ "resource_type": "content",
135
+ "shortname": "auto",
136
+ "subpath": "/users",
137
+ "attributes": {
138
+ "is_active": True,
139
+ "slug": None,
140
+ "displayname": {
141
+ "en": "name en",
142
+ "ar": "name ar",
143
+ "ku": "name ku",
144
+ },
145
+ "description": {
146
+ "en": "desc en",
147
+ "ar": "desc ar",
148
+ "ku": "desc ku",
149
+ },
150
+ "tags": [],
151
+ "payload": {
152
+ "content_type": "json",
153
+ "schema_shortname": "user",
154
+ "body": {
155
+ "email": "myname@gmail.com",
156
+ "first_name": "John",
157
+ "language": "en",
158
+ "last_name": "Doo",
159
+ "mobile": "7999311703",
160
+ },
161
+ },
162
+ },
163
+ }
164
+ ]
165
+ }
166
+ )
167
+
168
+ class Translation(Resource):
169
+ en: str | None = None
170
+ ar: str | None = None
171
+ ku: str | None = None
172
+
173
+
174
+ class Locator(Resource):
175
+ uuid: UUID | None = None
176
+ domain: str | None = None
177
+ type: ResourceType
178
+ space_name: str
179
+ subpath: str
180
+ shortname: str
181
+ schema_shortname: str | None = None
182
+ displayname: Translation | None = None
183
+ description: Translation | None = None
184
+ tags: list[str] | None = None
185
+
186
+
187
+ class Relationship(Resource):
188
+ related_to: Locator | dict[str, Any]
189
+ attributes: dict[str, Any]
190
+
191
+
192
+ class ACL(BaseModel):
193
+ user_shortname: str
194
+ allowed_actions: list[ActionType]
195
+
196
+
197
+ class Meta(Resource):
198
+ uuid: UUID = Field(default_factory=uuid4)
199
+ shortname: str = Field(pattern=regex.SHORTNAME)
200
+ slug: str | None = Field(default=None, pattern=regex.SLUG)
201
+ is_active: bool = False
202
+ displayname: Translation | None = None
203
+ description: Translation | None = None
204
+ tags: list[str] = []
205
+ created_at: datetime = Field(default_factory=datetime.now)
206
+ updated_at: datetime = Field(default_factory=datetime.now)
207
+ owner_shortname: str
208
+ owner_group_shortname: str | None = None
209
+ payload: Payload | None = None
210
+ relationships: list[Relationship] | list[dict[str, Any]] | None = None
211
+ acl: list[ACL] | None = None
212
+ last_checksum_history: str | None = Field(default=None)
213
+
214
+ model_config = ConfigDict(validate_assignment=True)
215
+
216
+ @staticmethod
217
+ def from_record(record: Record, owner_shortname: str):
218
+ if record.shortname == settings.auto_uuid_rule:
219
+ record.uuid = uuid4()
220
+ record.shortname = str(record.uuid)[:8]
221
+ record.attributes["uuid"] = record.uuid
222
+
223
+ meta_class = getattr(
224
+ sys.modules["models.core"], camel_case(record.resource_type)
225
+ )
226
+
227
+ if issubclass(meta_class, User) and "password" in record.attributes:
228
+ hashed_pass = password_hashing.hash_password(record.attributes["password"])
229
+ record.attributes["password"] = hashed_pass
230
+
231
+ record.attributes["owner_shortname"] = owner_shortname
232
+ record.attributes["shortname"] = record.shortname
233
+ return meta_class(**remove_none_dict(record.attributes))
234
+
235
+ @staticmethod
236
+ def check_record(record: Record, owner_shortname: str):
237
+ meta_class = getattr(
238
+ sys.modules["models.core"], camel_case(record.resource_type)
239
+ )
240
+
241
+ meta_obj = meta_class(
242
+ owner_shortname=owner_shortname,
243
+ shortname=record.shortname,
244
+ **record.attributes,
245
+ )
246
+ return meta_obj
247
+
248
+ def update_from_record(
249
+ self, record: Record, old_body: dict | None = None, replace: bool = False
250
+ ) -> dict | None:
251
+ restricted_fields = [
252
+ "uuid",
253
+ "shortname",
254
+ "created_at",
255
+ "updated_at",
256
+ "owner_shortname",
257
+ "payload",
258
+ "acl",
259
+ ]
260
+ for field_name, _ in self.__dict__.items():
261
+ if field_name in record.attributes and field_name not in restricted_fields:
262
+ if isinstance(self, User) and field_name == "password":
263
+ self.__setattr__(
264
+ field_name,
265
+ password_hashing.hash_password(record.attributes[field_name]),
266
+ )
267
+ continue
268
+
269
+ self.__setattr__(field_name, record.attributes[field_name])
270
+
271
+ if (
272
+ not self.payload
273
+ and "payload" in record.attributes
274
+ and record.attributes["payload"] is not None
275
+ and "content_type" in record.attributes["payload"]
276
+ ):
277
+ self.payload = Payload(
278
+ content_type=ContentType(record.attributes["payload"]["content_type"]),
279
+ schema_shortname=record.attributes["payload"].get("schema_shortname"),
280
+ body=f"{record.shortname}.json",
281
+ )
282
+
283
+ if self.payload and "payload" in record.attributes:
284
+ return self.payload.update(
285
+ payload=record.attributes["payload"], old_body=old_body, replace=replace
286
+ )
287
+ return None
288
+
289
+ def to_record(
290
+ self,
291
+ subpath: str,
292
+ shortname: str,
293
+ include: list[str] = [],
294
+ ) -> Record:
295
+ # Sanity check
296
+
297
+ if self.shortname != shortname:
298
+ raise Exception(
299
+ f"shortname in meta({subpath}/{self.shortname}) should be same as body({subpath}/{shortname})"
300
+ )
301
+
302
+ record_fields = {
303
+ "resource_type": snake_case(type(self).__name__),
304
+ "uuid": self.uuid,
305
+ "shortname": self.shortname,
306
+ "subpath": subpath,
307
+ }
308
+
309
+ attributes = {}
310
+ for key, value in self.__dict__.items():
311
+ if key == '_sa_instance_state':
312
+ continue
313
+ if (not include or key in include) and key not in record_fields:
314
+ attributes[key] = copy.deepcopy(value)
315
+
316
+ record_fields["attributes"] = attributes
317
+
318
+ return Record(**record_fields)
319
+
320
+
321
+ class Space(Meta):
322
+ root_registration_signature: str = ""
323
+ primary_website: str = ""
324
+ indexing_enabled: bool = False
325
+ capture_misses: bool = False
326
+ check_health: bool = False
327
+ languages: list[Language] = [Language.en]
328
+ icon: str = ""
329
+ mirrors: list[str] = []
330
+ hide_folders: list[str] = []
331
+ hide_space: bool | None = None
332
+ active_plugins: list[str] = []
333
+ ordinal: int | None = None
334
+
335
+
336
+ class Actor(Meta):
337
+ pass
338
+
339
+
340
+ class User(Actor):
341
+ password: str | None = None
342
+ email: str | None = None
343
+ msisdn: str | None = Field(default=None, pattern=regex.MSISDN)
344
+ locked_to_device: bool = False
345
+ is_email_verified: bool = False
346
+ is_msisdn_verified: bool = False
347
+ force_password_change: bool = False
348
+ type: UserType = UserType.web
349
+ roles: list[str] = []
350
+ groups: list[str] = []
351
+ firebase_token: str | None = None
352
+ language: Language = Language.ar
353
+ google_id: str | None = None
354
+ facebook_id: str | None = None
355
+ social_avatar_url: str | None = None
356
+ last_login: dict | None = None
357
+ notes: str | None = None
358
+
359
+ @staticmethod
360
+ def invitation_url_template() -> str:
361
+ return (
362
+ "{url}/auth/invitation?invitation={token}&lang={lang}&user-type={user_type}"
363
+ )
364
+
365
+
366
+ class Group(Meta):
367
+ roles: list[str] = [] # list of entitled roles
368
+
369
+
370
+ class Attachment(Meta):
371
+ author_locator: Locator | None = None
372
+
373
+
374
+ class DataAsset(Attachment):
375
+ pass
376
+
377
+
378
+ class Json(Attachment):
379
+ pass
380
+
381
+
382
+ class Jsonl(DataAsset):
383
+ pass
384
+
385
+
386
+ class Parquet(DataAsset):
387
+ pass
388
+
389
+
390
+ class Sqlite(DataAsset):
391
+ pass
392
+
393
+
394
+ class Duckdb(DataAsset):
395
+ pass
396
+
397
+
398
+ class Csv(DataAsset):
399
+ pass
400
+
401
+
402
+ class Share(Attachment):
403
+ pass
404
+
405
+
406
+ class Reaction(Attachment):
407
+ pass
408
+
409
+
410
+ class Reply(Attachment):
411
+ pass
412
+
413
+
414
+ class Comment(Attachment):
415
+ pass
416
+
417
+
418
+ class Lock(Attachment):
419
+ pass
420
+
421
+
422
+ class Media(Attachment):
423
+ pass
424
+
425
+
426
+ class Alteration(Attachment):
427
+ requested_update: dict
428
+
429
+
430
+ class Action(Resource):
431
+ resource: Locator
432
+ user_shortname: str
433
+ request: ActionType
434
+ timestamp: datetime
435
+ attributes: dict[str, Any] | None
436
+
437
+
438
+ class History(Meta):
439
+ timestamp: datetime
440
+ request_headers: dict[str, Any]
441
+ diff: dict[str, Any]
442
+
443
+
444
+ class Schema(Meta):
445
+ pass
446
+
447
+ # USE meta_schema TO VALIDATE ANY SCHEMA
448
+ def __init__(self, **data):
449
+ Meta.__init__(self, **data)
450
+ if self.payload is not None and self.shortname != "meta_schema":
451
+ self.payload.schema_shortname = "meta_schema"
452
+
453
+
454
+ class Content(Meta):
455
+ pass
456
+
457
+
458
+ class Log(Meta):
459
+ pass
460
+
461
+
462
+ class Folder(Meta):
463
+ pass
464
+
465
+
466
+ class Permission(Meta):
467
+ subpaths: dict[str, list[str]] # {"space_name": ["subpath_one", "subpath_two"]}
468
+ resource_types: list[ResourceType]
469
+ actions: list[ActionType]
470
+ conditions: list[ConditionType] = list()
471
+ restricted_fields: list[str] = []
472
+ allowed_fields_values: dict[str, list[str] | list[list[str]]] = {}
473
+ filter_fields_values: str | None = None
474
+
475
+
476
+ class Role(Meta):
477
+ permissions: list[str] # list of permissions_shortnames
478
+
479
+
480
+ # class Collabolator(Resource):
481
+ # role: str | None = "" # a free-text role-name e.g. developer, qa, admin, ...
482
+ # shortname: str
483
+
484
+
485
+ class Reporter(Resource):
486
+ type: str | None = None
487
+ name: str | None = None
488
+ channel: str | None = None
489
+ distributor: str | None = None
490
+ governorate: str | None = None
491
+ msisdn: str | None = None
492
+ channel_address: dict | None = None
493
+
494
+
495
+ class Ticket(Meta):
496
+ state: str
497
+ is_open: bool = True
498
+ reporter: Reporter | None = None
499
+ workflow_shortname: str
500
+ collaborators: dict[str, str] | None = None # list[Collabolator] | None = None
501
+ resolution_reason: str | None = None
502
+
503
+
504
+ class Post(Content):
505
+ pass
506
+
507
+
508
+ class Event(BaseModel):
509
+ space_name: str
510
+ subpath: str = Field(pattern=regex.SUBPATH)
511
+ shortname: str | None = Field(default=None, pattern=regex.SHORTNAME)
512
+ action_type: ActionType
513
+ resource_type: ResourceType | None = None
514
+ schema_shortname: str | None = None
515
+ attributes: dict = {}
516
+ user_shortname: str
517
+
518
+
519
+ class PluginBase(ABC):
520
+ @abstractmethod
521
+ async def hook(self, data: Event) -> None:
522
+ pass
523
+
524
+
525
+ class EventFilter(BaseModel):
526
+ subpaths: list
527
+ resource_types: list
528
+ schema_shortnames: list
529
+ actions: list[ActionType]
530
+
531
+
532
+ class PluginWrapper(Resource):
533
+ shortname: str = Field(pattern=regex.SHORTNAME)
534
+ is_active: bool = False
535
+ filters: EventFilter | None = None
536
+ listen_time: EventListenTime | None = None
537
+ type: PluginType | None = None
538
+ ordinal: int = 9999
539
+ object: PluginBase | None = None
540
+ dependencies: list = []
541
+
542
+
543
+ class NotificationData(Resource):
544
+ receiver: dict
545
+ title: Translation
546
+ body: Translation
547
+ image_urls: Translation | None = None
548
+ deep_link: dict = {}
549
+ entry_id: str | None = None
550
+
551
+
552
+ class Notification(Meta):
553
+ """
554
+ title: use the displayname attribute inside Meta
555
+ description: use the description attribute inside Meta
556
+ """
557
+
558
+ type: NotificationType
559
+ is_read: bool = False
560
+ priority: NotificationPriority
561
+ entry: Locator | None = None
562
+
563
+ @staticmethod
564
+ async def from_request(notification_req: dict, entry: dict = {}):
565
+ if (
566
+ notification_req["payload"]["schema_shortname"]
567
+ == "admin_notification_request"
568
+ ):
569
+ notification_type = NotificationType.admin
570
+ elif notification_req["payload"]["schema_shortname"] == "system_notification_request":
571
+ notification_type = NotificationType.system
572
+ else:
573
+ notification_type = NotificationType.admin
574
+
575
+ entry_locator = None
576
+ if entry:
577
+ entry_locator = Locator(
578
+ space_name=entry["space_name"],
579
+ type=ResourceType(entry["resource_type"]),
580
+ schema_shortname=entry["payload"]["schema_shortname"],
581
+ subpath=entry["subpath"],
582
+ shortname=entry["shortname"],
583
+ )
584
+
585
+ uuid = uuid4()
586
+ return Notification(
587
+ uuid=uuid,
588
+ shortname=str(uuid)[:8],
589
+ is_active=True,
590
+ displayname=notification_req["displayname"],
591
+ description=notification_req["description"],
592
+ owner_shortname=notification_req["owner_shortname"],
593
+ type=notification_type,
594
+ is_read=False,
595
+ priority=notification_req["payload"]["body"]["priority"],
596
+ entry=entry_locator,
597
+ )