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
api/user/service.py ADDED
@@ -0,0 +1,270 @@
1
+ import os
2
+ import random
3
+ import string
4
+ import time
5
+ from data_adapters.adapter import data_adapter as db
6
+ from models import core
7
+ from models.api import Error, Exception
8
+ from models.enums import ContentType
9
+ from utils import password_hashing
10
+ from utils.async_request import AsyncRequest
11
+ from utils.internal_error_code import InternalErrorCode
12
+ from utils.settings import settings
13
+ from fastapi.logger import logger
14
+ from fastapi import status
15
+
16
+
17
+ MANAGEMENT_SPACE: str = settings.management_space
18
+ USERS_SUBPATH: str = "users"
19
+
20
+ path = f"{os.path.dirname(__file__)}/mocks/"
21
+
22
+ headers = {"Content-Type": "application/json", "auth-key": settings.smpp_auth_key}
23
+
24
+
25
+ def gen_alphanumeric(length=16):
26
+ return "".join(
27
+ random.choice(string.ascii_letters + string.digits) for _ in range(length)
28
+ )
29
+
30
+
31
+ def gen_numeric(length=6):
32
+ return "".join(random.choice(string.digits) for _ in range(length))
33
+
34
+
35
+ async def mock_sending_otp(msisdn) -> dict:
36
+ key = f"users:otp:otps/{msisdn}"
37
+ await db.save_otp(key, settings.mock_otp_code)
38
+ json = {"status": "success", "data": {"status": "success"}}
39
+ return json
40
+
41
+ def get_otp_key(user_identifier: dict[str, str]) -> str:
42
+ if "msisdn" in user_identifier:
43
+ return f"users:otp:otps/{user_identifier['msisdn']}"
44
+ elif "email" in user_identifier:
45
+ return f"users:otp:otps/{user_identifier['email']}"
46
+ return ""
47
+
48
+ async def send_otp(msisdn: str, language: str):
49
+ json = {}
50
+ status: int
51
+ if settings.mock_smpp_api:
52
+ return await mock_sending_otp(msisdn)
53
+
54
+ # Creating SMS message body
55
+ code = gen_numeric()
56
+ message = ""
57
+ match language:
58
+ case "ckb":
59
+ message = f"کۆدی دڵنیابوونی تایبەت بەخۆت {code}"
60
+ case "en":
61
+ message = f"Your otp code is {code}"
62
+ case _:
63
+ message = f"رمز التحقق الخاص بك {code}"
64
+
65
+ await db.save_otp(f"users:otp:otps/{msisdn}", code)
66
+
67
+ async with AsyncRequest() as client:
68
+ response = await client.post(
69
+ settings.send_sms_otp_api,
70
+ headers={**headers, "skel-accept-language": language},
71
+ json={"msisdn": msisdn, "text": message},
72
+ )
73
+ json = await response.json()
74
+ status = response.status
75
+
76
+ if status != 200:
77
+ raise Exception(
78
+ status, Error(type="otp", code=InternalErrorCode.OTP_ISSUE, message="OTP issue", info=[json])
79
+ )
80
+
81
+ return json.get("data")
82
+
83
+
84
+ async def email_send_otp(email: str, language: str):
85
+ if settings.mock_smtp_api:
86
+ return await mock_sending_otp(email)
87
+
88
+ code = "".join(random.choice("0123456789") for _ in range(6))
89
+ await db.save_otp(f"users:otp:otps/{email}", code)
90
+ message = f"<p>Your OTP code is <b>{code}</b></p>"
91
+ return await send_email(settings.email_sender, email, message, "OTP", settings.send_email_otp_api)
92
+
93
+
94
+ async def send_sms(msisdn: str, message: str) -> bool:
95
+ json = {}
96
+ status: int
97
+ if settings.mock_smpp_api:
98
+ return True
99
+
100
+ async with AsyncRequest() as client:
101
+ response = await client.post(
102
+ settings.send_sms_api,
103
+ headers={**headers},
104
+ json={"msisdn": msisdn, "text": message},
105
+ )
106
+ json = await response.json()
107
+ status = response.status
108
+
109
+ if status != 200:
110
+ logger.warning(
111
+ "sms_sender_exception",
112
+ extra={
113
+ "props": {
114
+ "status": status,
115
+ "response": json,
116
+ "target": msisdn
117
+ }
118
+ },
119
+ )
120
+ return False
121
+
122
+ return True
123
+
124
+
125
+ async def send_email(from_address: str, to_address: str, message: str, subject: str, send_email_api=settings.send_email_api) -> bool:
126
+ json = {}
127
+ status: int
128
+ start_time = time.time()
129
+ if settings.mock_smtp_api:
130
+ return True
131
+
132
+ async with AsyncRequest() as client:
133
+ response = await client.post(
134
+ send_email_api,
135
+ headers={**headers},
136
+ json={
137
+ "from_address": from_address,
138
+ "to": to_address,
139
+ "msg": message,
140
+ "subject": subject,
141
+ },
142
+ )
143
+ json = await response.json()
144
+ status = response.status
145
+ logger.info(
146
+ "Email Service",
147
+ extra={
148
+ "props": {
149
+ "duration": 1000 * (time.time() - start_time),
150
+ "request": {
151
+ "from_address": from_address,
152
+ "to": to_address,
153
+ "msg": message,
154
+ },
155
+ "response": {"status": status, "json": json},
156
+ }
157
+ },
158
+ )
159
+
160
+ if status != 200:
161
+ logger.warning(
162
+ "Email Service",
163
+ extra={
164
+ "props": {
165
+ "status": status,
166
+ "response": json,
167
+ "target": to_address,
168
+ "sender": from_address
169
+ }
170
+ },
171
+ )
172
+ return False
173
+
174
+ return True
175
+
176
+ async def get_shortname_from_identifier(key, value):
177
+ if isinstance(value, str) and isinstance(key, str):
178
+ shortname = await db.get_user_by_criteria(key, value)
179
+ if shortname is None:
180
+ raise Exception(
181
+ status.HTTP_404_NOT_FOUND,
182
+ Error(
183
+ type="auth",
184
+ code=InternalErrorCode.SHORTNAME_DOES_NOT_EXIST,
185
+ message="User not found",
186
+ )
187
+ )
188
+ if not (await db.is_user_verified(shortname, key)):
189
+ raise Exception(
190
+ status.HTTP_401_UNAUTHORIZED,
191
+ Error(
192
+ type="auth",
193
+ code=InternalErrorCode.USER_ISNT_VERIFIED,
194
+ message="This user is not verified",
195
+ ),
196
+ )
197
+ return shortname
198
+ else:
199
+ return None
200
+
201
+
202
+ def check_user_validation(user, data, user_updates, invitation_token):
203
+ if (
204
+ data.get("channel") == "EMAIL"
205
+ and user.email
206
+ and f"EMAIL:{user.email}" in invitation_token
207
+ ):
208
+ user_updates["is_email_verified"] = True
209
+ elif (
210
+ data.get("channel") == "SMS"
211
+ and user.msisdn
212
+ and f"SMS:{user.msisdn}" in invitation_token
213
+ ):
214
+ user_updates["is_msisdn_verified"] = True
215
+
216
+ return user_updates
217
+
218
+
219
+ async def set_user_profile(profile, profile_user, user):
220
+ if profile_user.password:
221
+ user.password = password_hashing.hash_password(profile_user.password)
222
+ user.force_password_change = False
223
+ # Clear the failed password attempts
224
+ await db.clear_failed_password_attempts(profile.shortname)
225
+ if "displayname" in profile.attributes:
226
+ user.displayname = profile_user.displayname.model_dump()
227
+ if "description" in profile.attributes:
228
+ user.description = profile_user.description.model_dump()
229
+ if "language" in profile.attributes:
230
+ user.language = profile_user.language
231
+ if "is_active" in profile.attributes:
232
+ user.is_active = profile_user.is_active
233
+ return user
234
+
235
+
236
+ async def get_otp_confirmation_email_or_msisdn(profile_user):
237
+ if profile_user.email:
238
+ return await db.get_otp(f"users:otp:confirmation/email/{profile_user.email}")
239
+ elif profile_user.msisdn:
240
+ return await db.get_otp(f"users:otp:confirmation/msisdn/{profile_user.msisdn}")
241
+ return None
242
+
243
+
244
+ async def update_user_payload(profile, profile_user, user, shortname):
245
+ separate_payload_data = {}
246
+ user.payload = core.Payload(
247
+ content_type=ContentType.json,
248
+ schema_shortname=profile_user.payload.schema_shortname,
249
+ body="",
250
+ )
251
+ if profile.attributes["payload"]["body"]:
252
+ separate_payload_data = profile.attributes["payload"]["body"]
253
+ if settings.active_data_db == "file":
254
+ user.payload.body = f"{shortname}.json"
255
+
256
+ if user.payload and separate_payload_data:
257
+ if profile_user.payload.schema_shortname:
258
+ await db.validate_payload_with_schema(
259
+ payload_data=separate_payload_data,
260
+ space_name=MANAGEMENT_SPACE,
261
+ schema_shortname=str(user.payload.schema_shortname),
262
+ )
263
+
264
+ await db.save_payload_from_json(
265
+ MANAGEMENT_SPACE,
266
+ USERS_SUBPATH,
267
+ user,
268
+ separate_payload_data,
269
+ )
270
+
bundler.py ADDED
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env -S BACKEND_ENV=config.env python3
2
+ import json
3
+ import subprocess
4
+ import PyInstaller.__main__
5
+ import os
6
+
7
+ branch_cmd = "git rev-parse --abbrev-ref HEAD"
8
+ result, _ = subprocess.Popen(branch_cmd.split(" "), stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
9
+ branch = None if result is None or len(result) == 0 else result.decode().strip()
10
+
11
+ version_cmd = "git rev-parse --short HEAD"
12
+ result, _ = subprocess.Popen(version_cmd.split(" "), stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
13
+ version = None if result is None or len(result) == 0 else result.decode().strip()
14
+
15
+ tag_cmd = "git describe --tags"
16
+ result, _ = subprocess.Popen(tag_cmd.split(" "), stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
17
+ tag = None if result is None or len(result) == 0 else result.decode().strip()
18
+
19
+ version_date_cmd = "git show --pretty=format:'%ad'"
20
+ result, _ = subprocess.Popen(version_date_cmd.split(" "), stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
21
+ version_date = None if result is None or len(result) == 0 else result.decode().split("\n")[0]
22
+
23
+ info = {
24
+ "branch": branch,
25
+ "version": version,
26
+ "tag": tag,
27
+ "version_date": version_date
28
+ }
29
+
30
+ json.dump(info, open('info.json', 'w'))
31
+
32
+ args = [
33
+ 'dmart.py',
34
+ '--name=dmart',
35
+ '--onefile',
36
+ '--runtime-tmpdir=.',
37
+ '--distpath=.',
38
+ '--add-data=./info.json:.',
39
+ '--add-data=dmart_migrations:dmart_migrations',
40
+ '--add-data=alembic.ini:.',
41
+ '--noconfirm',
42
+ '--collect-submodules=concurrent_log_handler',
43
+ '--collect-submodules=pythonjsonlogger',
44
+ '--collect-all=alembic',
45
+ '--clean',
46
+ ]
47
+
48
+ cxb_path = 'cxb'
49
+ if not os.path.isdir(cxb_path):
50
+ cxb_path = '../cxb/dist/client'
51
+
52
+ if os.path.isdir(cxb_path):
53
+ args.append(f'--add-data={cxb_path}:cxb')
54
+
55
+ PyInstaller.__main__.run(args)
config/__init__.py ADDED
File without changes
config/channels.json ADDED
@@ -0,0 +1,11 @@
1
+ [
2
+ {
3
+ "name": "web",
4
+ "keys": [
5
+ "12345"
6
+ ],
7
+ "allowed_api_patterns": [
8
+ "/managed/*"
9
+ ]
10
+ }
11
+ ]
@@ -0,0 +1,17 @@
1
+ {
2
+ "web": {
3
+ "active": true,
4
+ "module": "utils.web_notifier",
5
+ "class": "WebNotifier"
6
+ },
7
+ "sms": {
8
+ "active": true,
9
+ "module": "utils.sms_notifier",
10
+ "class": "SMSNotifier"
11
+ },
12
+ "mobile": {
13
+ "active": true,
14
+ "module": "utils.firebase_notifier",
15
+ "class": "FirebaseNotifier"
16
+ }
17
+ }
cxb/__init__.py ADDED
File without changes
cxb/client/__init__.py ADDED
File without changes