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.
- alembic.ini +117 -0
- api/__init__.py +0 -0
- api/info/__init__.py +0 -0
- api/info/router.py +109 -0
- api/managed/__init__.py +0 -0
- api/managed/router.py +1541 -0
- api/managed/utils.py +1879 -0
- api/public/__init__.py +0 -0
- api/public/router.py +758 -0
- api/qr/__init__.py +0 -0
- api/qr/router.py +108 -0
- api/user/__init__.py +0 -0
- api/user/model/__init__.py +0 -0
- api/user/model/errors.py +14 -0
- api/user/model/requests.py +165 -0
- api/user/model/responses.py +11 -0
- api/user/router.py +1413 -0
- api/user/service.py +270 -0
- bundler.py +55 -0
- config/__init__.py +0 -0
- config/channels.json +11 -0
- config/notification.json +17 -0
- cxb/__init__.py +0 -0
- cxb/client/__init__.py +0 -0
- cxb/client/assets/@codemirror-Rn7_6DkE.js +10 -0
- cxb/client/assets/@edraj-CS4NwVbD.js +1 -0
- cxb/client/assets/@floating-ui-BwwcF-xh.js +1 -0
- cxb/client/assets/@formatjs-yKEsAtjs.js +1 -0
- cxb/client/assets/@fortawesome-DRW1UCdr.js +9 -0
- cxb/client/assets/@jsonquerylang-laKNoFFq.js +12 -0
- cxb/client/assets/@lezer-za4Q-8Ew.js +1 -0
- cxb/client/assets/@marijn-DXwl3gUT.js +1 -0
- cxb/client/assets/@popperjs-l0sNRNKZ.js +1 -0
- cxb/client/assets/@replit--ERk53eB.js +1 -0
- cxb/client/assets/@roxi-CGMFK4i8.js +6 -0
- cxb/client/assets/@typewriter-cCzskkIv.js +17 -0
- cxb/client/assets/@zerodevx-BlBZjKxu.js +1 -0
- cxb/client/assets/@zerodevx-CVEpe6WZ.css +1 -0
- cxb/client/assets/BreadCrumbLite-DAhOx38v.js +1 -0
- cxb/client/assets/EntryRenderer-25YDhRen.js +32 -0
- cxb/client/assets/EntryRenderer-DXytdFp9.css +1 -0
- cxb/client/assets/ListView-BpAycA2h.js +16 -0
- cxb/client/assets/ListView-U8of-_c-.css +1 -0
- cxb/client/assets/Prism--hMplq-p.js +3 -0
- cxb/client/assets/Prism-Uh6uStUw.css +1 -0
- cxb/client/assets/Table2Cols-BsbwicQm.js +1 -0
- cxb/client/assets/_..-BvT6vdHa.css +1 -0
- cxb/client/assets/_...404_-fuLH_rX9.js +2 -0
- cxb/client/assets/_...fallback_-Ba_NLmAE.js +1 -0
- cxb/client/assets/_module-Bfk8MiCs.js +3 -0
- cxb/client/assets/_module-CEW0D5oI.js +4 -0
- cxb/client/assets/_module-Dgq0ZVtz.js +1 -0
- cxb/client/assets/ajv-Cpj98o6Y.js +1 -0
- cxb/client/assets/axios-CG2WSiiR.js +6 -0
- cxb/client/assets/clsx-B-dksMZM.js +1 -0
- cxb/client/assets/codemirror-wrapped-line-indent-DPhKvljI.js +1 -0
- cxb/client/assets/compare-C3AjiGFR.js +1 -0
- cxb/client/assets/compute-scroll-into-view-Bl8rNFhg.js +1 -0
- cxb/client/assets/consolite-DlCuI0F9.js +1 -0
- cxb/client/assets/crelt-C8TCjufn.js +1 -0
- cxb/client/assets/date-fns-l0sNRNKZ.js +1 -0
- cxb/client/assets/deepmerge-rn4rBaHU.js +1 -0
- cxb/client/assets/dmart_services-AL6-IdDE.js +1 -0
- cxb/client/assets/downloadFile-D08i0YDh.js +1 -0
- cxb/client/assets/easy-signal-BiPFIK3O.js +1 -0
- cxb/client/assets/esm-env-rsSWfq8L.js +1 -0
- cxb/client/assets/export-OF_rTiXu.js +1 -0
- cxb/client/assets/fast-deep-equal-l0sNRNKZ.js +1 -0
- cxb/client/assets/fast-diff-C-IidNf4.js +1 -0
- cxb/client/assets/fast-uri-l0sNRNKZ.js +1 -0
- cxb/client/assets/flowbite-svelte-BLvjb-sa.js +1 -0
- cxb/client/assets/flowbite-svelte-CD54FDqW.css +1 -0
- cxb/client/assets/flowbite-svelte-icons-BI8GVhw_.js +1 -0
- cxb/client/assets/github-slugger-CQ4oX9Ud.js +1 -0
- cxb/client/assets/global-igKv-1g9.js +1 -0
- cxb/client/assets/hookar-BMRD9G9H.js +1 -0
- cxb/client/assets/immutable-json-patch-DtRO2E_S.js +1 -0
- cxb/client/assets/import-1vE3gBat.js +1 -0
- cxb/client/assets/index-B-eTh-ZX.js +1 -0
- cxb/client/assets/index-BVyxzKtH.js +1 -0
- cxb/client/assets/index-BdeNM69f.js +1 -0
- cxb/client/assets/index-C6cPO4op.js +1 -0
- cxb/client/assets/index-CC-A1ipE.js +1 -0
- cxb/client/assets/index-CTxJ-lDp.js +1 -0
- cxb/client/assets/index-Cd-F5j_k.js +1 -0
- cxb/client/assets/index-D742rwaM.js +1 -0
- cxb/client/assets/index-DTfhnhwd.js +1 -0
- cxb/client/assets/index-DdXRK7n9.js +2 -0
- cxb/client/assets/index-DtiCmB4o.js +1 -0
- cxb/client/assets/index-NBrXBlLA.css +2 -0
- cxb/client/assets/index-ac-Buu_H.js +4 -0
- cxb/client/assets/index-iYkH7C67.js +1 -0
- cxb/client/assets/info-B986lRiM.js +1 -0
- cxb/client/assets/intl-messageformat-Dc5UU-HB.js +3 -0
- cxb/client/assets/jmespath-l0sNRNKZ.js +1 -0
- cxb/client/assets/json-schema-traverse-l0sNRNKZ.js +1 -0
- cxb/client/assets/json-source-map-DRgZidqy.js +5 -0
- cxb/client/assets/jsonpath-plus-l0sNRNKZ.js +1 -0
- cxb/client/assets/jsonrepair-B30Dx381.js +8 -0
- cxb/client/assets/lodash-es-DZVAA2ox.js +1 -0
- cxb/client/assets/marked-DKjyhwJX.js +56 -0
- cxb/client/assets/marked-gfm-heading-id-U5zO829x.js +2 -0
- cxb/client/assets/marked-mangle-CDMeiHC6.js +1 -0
- cxb/client/assets/memoize-one-BdPwpGay.js +1 -0
- cxb/client/assets/natural-compare-lite-Bg2Xcf-o.js +7 -0
- cxb/client/assets/pagination-svelte-D5CyoiE_.js +13 -0
- cxb/client/assets/pagination-svelte-v10nAbbM.css +1 -0
- cxb/client/assets/plantuml-encoder-C47mzt9T.js +1 -0
- cxb/client/assets/prismjs-DTUiLGJu.js +9 -0
- cxb/client/assets/profile-BUf-tKMe.js +1 -0
- cxb/client/assets/query-CNmXTsgf.js +1 -0
- cxb/client/assets/queryHelpers-C9iBWwqe.js +1 -0
- cxb/client/assets/scroll-into-view-if-needed-KR58zyjF.js +1 -0
- cxb/client/assets/spaces-0oyGvpii.js +1 -0
- cxb/client/assets/style-mod-Bs6eFhZE.js +3 -0
- cxb/client/assets/svelte-B2XmcTi_.js +4 -0
- cxb/client/assets/svelte-awesome-COLlx0DN.css +1 -0
- cxb/client/assets/svelte-awesome-DhnMA6Q_.js +1 -0
- cxb/client/assets/svelte-datatables-net-CY7LBj6I.js +1 -0
- cxb/client/assets/svelte-floating-ui-BlS3sOAQ.js +1 -0
- cxb/client/assets/svelte-i18n-CT2KkQaN.js +3 -0
- cxb/client/assets/svelte-jsoneditor-BzfX6Usi.css +1 -0
- cxb/client/assets/svelte-jsoneditor-CUGSvWId.js +25 -0
- cxb/client/assets/svelte-select-CegQKzqH.css +1 -0
- cxb/client/assets/svelte-select-CjHAt_85.js +6 -0
- cxb/client/assets/tailwind-merge-CJvxXMcu.js +1 -0
- cxb/client/assets/tailwind-variants-Cj20BoQ3.js +1 -0
- cxb/client/assets/toast-B9WDyfyI.js +1 -0
- cxb/client/assets/tslib-pJfR_DrR.js +1 -0
- cxb/client/assets/typewriter-editor-DkTVIJdm.js +25 -0
- cxb/client/assets/user-DeK_NB5v.js +1 -0
- cxb/client/assets/vanilla-picker-l5rcX3cq.js +8 -0
- cxb/client/assets/w3c-keyname-Vcq4gwWv.js +1 -0
- cxb/client/config.json +11 -0
- cxb/client/config.sample.json +11 -0
- cxb/client/favicon.ico +0 -0
- cxb/client/favicon.png +0 -0
- cxb/client/index.html +28 -0
- data_adapters/__init__.py +0 -0
- data_adapters/adapter.py +16 -0
- data_adapters/base_data_adapter.py +467 -0
- data_adapters/file/__init__.py +0 -0
- data_adapters/file/adapter.py +2043 -0
- data_adapters/file/adapter_helpers.py +1013 -0
- data_adapters/file/archive.py +150 -0
- data_adapters/file/create_index.py +331 -0
- data_adapters/file/create_users_folders.py +52 -0
- data_adapters/file/custom_validations.py +68 -0
- data_adapters/file/drop_index.py +40 -0
- data_adapters/file/health_check.py +560 -0
- data_adapters/file/redis_services.py +1110 -0
- data_adapters/helpers.py +27 -0
- data_adapters/sql/__init__.py +0 -0
- data_adapters/sql/adapter.py +3218 -0
- data_adapters/sql/adapter_helpers.py +491 -0
- data_adapters/sql/create_tables.py +451 -0
- data_adapters/sql/create_users_folders.py +53 -0
- data_adapters/sql/db_to_json_migration.py +485 -0
- data_adapters/sql/health_check_sql.py +232 -0
- data_adapters/sql/json_to_db_migration.py +454 -0
- data_adapters/sql/update_query_policies.py +101 -0
- data_generator.py +81 -0
- dmart-1.4.17.dist-info/METADATA +65 -0
- dmart-1.4.17.dist-info/RECORD +289 -0
- dmart-1.4.17.dist-info/WHEEL +5 -0
- dmart-1.4.17.dist-info/entry_points.txt +2 -0
- dmart-1.4.17.dist-info/top_level.txt +24 -0
- dmart.py +623 -0
- dmart_migrations/README +1 -0
- dmart_migrations/__init__.py +0 -0
- dmart_migrations/__pycache__/__init__.cpython-314.pyc +0 -0
- dmart_migrations/__pycache__/env.cpython-314.pyc +0 -0
- dmart_migrations/env.py +100 -0
- dmart_migrations/notes.txt +11 -0
- dmart_migrations/script.py.mako +28 -0
- dmart_migrations/scripts/__init__.py +0 -0
- dmart_migrations/scripts/calculate_checksums.py +77 -0
- dmart_migrations/scripts/migration_f7a4949eed19.py +28 -0
- dmart_migrations/versions/0f3d2b1a7c21_add_authz_materialized_views.py +87 -0
- dmart_migrations/versions/10d2041b94d4_last_checksum_history.py +62 -0
- dmart_migrations/versions/1cf4e1ee3cb8_ext_permission_with_filter_fields_values.py +33 -0
- dmart_migrations/versions/26bfe19b49d4_rm_failedloginattempts.py +42 -0
- dmart_migrations/versions/3c8bca2219cc_add_otp_table.py +38 -0
- dmart_migrations/versions/6675fd9dfe42_remove_unique_from_sessions_table.py +36 -0
- dmart_migrations/versions/71bc1df82e6a_adding_user_last_login_at.py +43 -0
- dmart_migrations/versions/74288ccbd3b5_initial.py +264 -0
- dmart_migrations/versions/7520a89a8467_rm_activesession_table.py +39 -0
- dmart_migrations/versions/848b623755a4_make_created_nd_updated_at_required.py +138 -0
- dmart_migrations/versions/8640dcbebf85_add_notes_to_users.py +32 -0
- dmart_migrations/versions/91c94250232a_adding_fk_on_owner_shortname.py +104 -0
- dmart_migrations/versions/98ecd6f56f9a_ext_meta_with_owner_group_shortname.py +66 -0
- dmart_migrations/versions/9aae9138c4ef_indexing_created_at_updated_at.py +80 -0
- dmart_migrations/versions/__init__.py +0 -0
- dmart_migrations/versions/__pycache__/0f3d2b1a7c21_add_authz_materialized_views.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/10d2041b94d4_last_checksum_history.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/1cf4e1ee3cb8_ext_permission_with_filter_fields_values.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/26bfe19b49d4_rm_failedloginattempts.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/3c8bca2219cc_add_otp_table.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/6675fd9dfe42_remove_unique_from_sessions_table.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/71bc1df82e6a_adding_user_last_login_at.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/74288ccbd3b5_initial.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/7520a89a8467_rm_activesession_table.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/848b623755a4_make_created_nd_updated_at_required.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/8640dcbebf85_add_notes_to_users.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/91c94250232a_adding_fk_on_owner_shortname.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/98ecd6f56f9a_ext_meta_with_owner_group_shortname.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/9aae9138c4ef_indexing_created_at_updated_at.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/b53f916b3f6d_json_to_jsonb.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/eb5f1ec65156_adding_user_locked_to_device.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/f7a4949eed19_adding_query_policies_to_meta.cpython-314.pyc +0 -0
- dmart_migrations/versions/b53f916b3f6d_json_to_jsonb.py +492 -0
- dmart_migrations/versions/eb5f1ec65156_adding_user_locked_to_device.py +36 -0
- dmart_migrations/versions/f7a4949eed19_adding_query_policies_to_meta.py +60 -0
- get_settings.py +7 -0
- info.json +1 -0
- languages/__init__.py +0 -0
- languages/arabic.json +15 -0
- languages/english.json +16 -0
- languages/kurdish.json +14 -0
- languages/loader.py +12 -0
- main.py +560 -0
- migrate.py +24 -0
- models/__init__.py +0 -0
- models/api.py +203 -0
- models/core.py +597 -0
- models/enums.py +255 -0
- password_gen.py +8 -0
- plugins/__init__.py +0 -0
- plugins/action_log/__init__.py +0 -0
- plugins/action_log/plugin.py +121 -0
- plugins/admin_notification_sender/__init__.py +0 -0
- plugins/admin_notification_sender/plugin.py +124 -0
- plugins/ldap_manager/__init__.py +0 -0
- plugins/ldap_manager/plugin.py +100 -0
- plugins/local_notification/__init__.py +0 -0
- plugins/local_notification/plugin.py +123 -0
- plugins/realtime_updates_notifier/__init__.py +0 -0
- plugins/realtime_updates_notifier/plugin.py +58 -0
- plugins/redis_db_update/__init__.py +0 -0
- plugins/redis_db_update/plugin.py +188 -0
- plugins/resource_folders_creation/__init__.py +0 -0
- plugins/resource_folders_creation/plugin.py +81 -0
- plugins/system_notification_sender/__init__.py +0 -0
- plugins/system_notification_sender/plugin.py +188 -0
- plugins/update_access_controls/__init__.py +0 -0
- plugins/update_access_controls/plugin.py +9 -0
- pytests/__init__.py +0 -0
- pytests/api_user_models_erros_test.py +16 -0
- pytests/api_user_models_requests_test.py +98 -0
- pytests/archive_test.py +72 -0
- pytests/base_test.py +300 -0
- pytests/get_settings_test.py +14 -0
- pytests/json_to_db_migration_test.py +237 -0
- pytests/service_test.py +26 -0
- pytests/test_info.py +55 -0
- pytests/test_status.py +15 -0
- run_notification_campaign.py +85 -0
- scheduled_notification_handler.py +121 -0
- schema_migration.py +208 -0
- schema_modulate.py +192 -0
- set_admin_passwd.py +55 -0
- sync.py +202 -0
- utils/__init__.py +0 -0
- utils/access_control.py +306 -0
- utils/async_request.py +8 -0
- utils/exporter.py +309 -0
- utils/firebase_notifier.py +57 -0
- utils/generate_email.py +37 -0
- utils/helpers.py +352 -0
- utils/hypercorn_config.py +12 -0
- utils/internal_error_code.py +60 -0
- utils/jwt.py +124 -0
- utils/logger.py +167 -0
- utils/middleware.py +99 -0
- utils/notification.py +75 -0
- utils/password_hashing.py +16 -0
- utils/plugin_manager.py +202 -0
- utils/query_policies_helper.py +128 -0
- utils/regex.py +44 -0
- utils/repository.py +529 -0
- utils/router_helper.py +19 -0
- utils/settings.py +166 -0
- utils/sms_notifier.py +21 -0
- utils/social_sso.py +67 -0
- utils/templates/activation.html.j2 +26 -0
- utils/templates/reminder.html.j2 +17 -0
- utils/ticket_sys_utils.py +203 -0
- utils/web_notifier.py +29 -0
- websocket.py +231 -0
utils/regex.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
SUBPATH = "^[a-zA-Z\u0621-\u064A0-9\u0660-\u0669\u064B-\u065F_/]{1,128}$"
|
|
4
|
+
SHORTNAME = "^[a-zA-Z\u0621-\u064A0-9\u0660-\u0669\u064B-\u065F_]{1,64}$"
|
|
5
|
+
SLUG = "^[a-zA-Z0-9_-]{1,64}$"
|
|
6
|
+
FILENAME = "^[a-zA-Z\u0621-\u064A0-9\u0660-\u0669\u064B-\u065F_]{1,32}\\.(gif|png|jpeg|jpg|pdf|wsq|mp3|mp4|csv|jsonl|parquet|sqlite|sqlite3|sqlite|db|duckdb|svg|apk)$"
|
|
7
|
+
SPACENAME = "^[a-zA-Z\u0621-\u064A0-9\u0660-\u0669\u064B-\u065F_]{1,32}$"
|
|
8
|
+
EXT = "^(gif|png|jpeg|jpg|webp|json|md|pdf|wsq|mp3|mp4|csv|jsonl|parquet|sqlite|sqlite3|db|db3|s3db|sl3|duckdb|svg|xsls|docx|apk)$"
|
|
9
|
+
IMG_EXT = "^(gif|png|jpeg|jpg|wsq|svg|webp)$"
|
|
10
|
+
USERNAME = "^[a-zA-Z\u0621-\u064A0-9\u0660-\u0669\u064B-\u065F_]{3,10}$"
|
|
11
|
+
PASSWORD = "^(?=.*[0-9\u0660-\u0669])(?=.*[A-Z\u0621-\u064A])[a-zA-Z\u0621-\u064A0-9\u0660-\u0669_#@%*!?$^-]{8,24}$"
|
|
12
|
+
EMAIL = (
|
|
13
|
+
"^[a-zA-Z\u0621-\u064A0-9\u0660-\u0669_\\.-]+@([a-zA-Z\u0621-\u064A0-9\u0660-\u0669_-]+\\.)+"
|
|
14
|
+
"[a-zA-Z\u0621-\u064A0-9\u0660-\u0669_-]{2,4}$"
|
|
15
|
+
)
|
|
16
|
+
META_DOC_ID = (
|
|
17
|
+
"^[a-zA-Z\u0621-\u064A0-9\u0660-\u0669_]*:[a-zA-Z\u0621-\u064A0-9\u0660-\u0669_]"
|
|
18
|
+
"[a-zA-Z\u0621-\u064A0-9\u0660-\u0669_]*:meta:[a-zA-Z\u0621-\u064A0-9\u0660-\u0669_/]+$"
|
|
19
|
+
)
|
|
20
|
+
MSISDN = "^[1-9][0-9]{9, 14}$" # Between 10 and 14 digits, not starting with zero
|
|
21
|
+
EXTENDED_MSISDN = (
|
|
22
|
+
"^[1-9][0-9]{9,14}$" # Between 10 and 14 digits, not starting with zero
|
|
23
|
+
)
|
|
24
|
+
OTP_CODE = "^[0-9\u0660-\u0669]{6}$" # Exactly 6 digits
|
|
25
|
+
INVITATION = (
|
|
26
|
+
"^([a-zA-Z\u0621-\u064A0-9\u0660-\u0669_=]+)\\.([a-zA-Z\u0621-\u064A0-9\u0660-\u0669_=]+)"
|
|
27
|
+
"\\.([a-zA-Z\u0621-\u064A0-9\u0660-\u0669_+/=-]*)$"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
FILE_PATTERN = re.compile(
|
|
31
|
+
"\\.dm/([a-zA-Z\u0621-\u064A0-9\u0660-\u0669_]*)/meta\\.([a-zA-Z\u0621-\u064A]*)\\.json$"
|
|
32
|
+
)
|
|
33
|
+
PAYLOAD_FILE_PATTERN = re.compile("([a-zA-Z\u0621-\u064A0-9\u0660-\u0669_]*)\\.json$")
|
|
34
|
+
# HISTORY_PATTERN = re.compile("([0-9\u0660-\u0669]*)\\.json$")
|
|
35
|
+
ATTACHMENT_PATTERN = re.compile(
|
|
36
|
+
"attachments\\.*[a-zA-Z\u0621-\u064A0-9\u0660-\u0669_]*\\.([a-zA-Z\u0621-\u064A0-9\u0660-\u0669_]+)"
|
|
37
|
+
"/meta\\.([a-zA-Z\u0621-\u064A0-9\u0660-\u0669_]*)\\.json$"
|
|
38
|
+
)
|
|
39
|
+
FOLDER_PATTERN = re.compile(
|
|
40
|
+
"/([a-zA-Z\u0621-\u064A0-9\u0660-\u0669\u064B-\u065F_]*)/\\.dm/meta\\.folder\\.json$"
|
|
41
|
+
)
|
|
42
|
+
SPACES_PATTERN = re.compile(
|
|
43
|
+
"/([a-zA-Z\u0621-\u064A0-9\u0660-\u0669\u064B-\u065F_]*)/\\.dm/meta\\.space\\.json$"
|
|
44
|
+
)
|
utils/repository.py
ADDED
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import sys
|
|
4
|
+
import asyncio
|
|
5
|
+
import json
|
|
6
|
+
import subprocess
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
from uuid import uuid4
|
|
10
|
+
from fastapi import status
|
|
11
|
+
import models.api as api
|
|
12
|
+
import models.core as core
|
|
13
|
+
import utils.regex as regex
|
|
14
|
+
from models.enums import ContentType, Language
|
|
15
|
+
from data_adapters.adapter import data_adapter as db
|
|
16
|
+
from utils.helpers import (
|
|
17
|
+
camel_case,
|
|
18
|
+
jq_dict_parser,
|
|
19
|
+
)
|
|
20
|
+
from utils.internal_error_code import InternalErrorCode
|
|
21
|
+
from utils.jwt import generate_jwt
|
|
22
|
+
from utils.settings import settings
|
|
23
|
+
|
|
24
|
+
async def serve_query(
|
|
25
|
+
query: api.Query, logged_in_user: str
|
|
26
|
+
) -> tuple[int, list[core.Record]]:
|
|
27
|
+
records: list[core.Record] = []
|
|
28
|
+
total: int = 0
|
|
29
|
+
|
|
30
|
+
total, records = await db.query(query, logged_in_user)
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
for _r in records or []:
|
|
34
|
+
attrs = getattr(_r, "attributes", None)
|
|
35
|
+
if isinstance(attrs, dict):
|
|
36
|
+
attrs.pop("password", None)
|
|
37
|
+
except Exception:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
if query.jq_filter:
|
|
41
|
+
try:
|
|
42
|
+
def _run_jq_subprocess() -> list:
|
|
43
|
+
_input_local = [record.model_dump() for record in records]
|
|
44
|
+
_input_local = jq_dict_parser(_input_local)
|
|
45
|
+
input_json = json.dumps(_input_local)
|
|
46
|
+
|
|
47
|
+
cmd = ["jq", "-c", query.jq_filter]
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
completed = subprocess.run(
|
|
51
|
+
cmd, # type: ignore
|
|
52
|
+
input=input_json.encode("utf-8"),
|
|
53
|
+
stdout=subprocess.PIPE,
|
|
54
|
+
stderr=subprocess.PIPE,
|
|
55
|
+
timeout=settings.jq_timeout,
|
|
56
|
+
check=False,
|
|
57
|
+
)
|
|
58
|
+
except subprocess.TimeoutExpired:
|
|
59
|
+
raise api.Exception(
|
|
60
|
+
status.HTTP_400_BAD_REQUEST,
|
|
61
|
+
api.Error(
|
|
62
|
+
type="request",
|
|
63
|
+
code=InternalErrorCode.JQ_TIMEOUT,
|
|
64
|
+
message="jq filter took too long to execute",
|
|
65
|
+
),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if completed.returncode != 0:
|
|
69
|
+
raise api.Exception(
|
|
70
|
+
status.HTTP_400_BAD_REQUEST,
|
|
71
|
+
api.Error(
|
|
72
|
+
type="request",
|
|
73
|
+
code=InternalErrorCode.JQ_ERROR,
|
|
74
|
+
message="jq filter failed to be executed",
|
|
75
|
+
),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
stdout = completed.stdout.decode("utf-8")
|
|
79
|
+
results: list = []
|
|
80
|
+
if stdout.startswith("[") and stdout.endswith("]\n"):
|
|
81
|
+
results = json.loads(stdout)
|
|
82
|
+
else:
|
|
83
|
+
for line in stdout.splitlines():
|
|
84
|
+
line = line.strip()
|
|
85
|
+
if not line:
|
|
86
|
+
continue
|
|
87
|
+
results.append(json.loads(line))
|
|
88
|
+
return results
|
|
89
|
+
|
|
90
|
+
loop = asyncio.get_running_loop()
|
|
91
|
+
records = await asyncio.wait_for(
|
|
92
|
+
loop.run_in_executor(None, _run_jq_subprocess),
|
|
93
|
+
timeout=settings.jq_timeout,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
except FileNotFoundError:
|
|
97
|
+
raise api.Exception(
|
|
98
|
+
status.HTTP_400_BAD_REQUEST,
|
|
99
|
+
api.Error(
|
|
100
|
+
type="request",
|
|
101
|
+
code=InternalErrorCode.NOT_ALLOWED,
|
|
102
|
+
message="jq is not installed!",
|
|
103
|
+
),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
return total, records
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
async def get_last_updated_entry(
|
|
110
|
+
space_name: str,
|
|
111
|
+
schema_names: list,
|
|
112
|
+
retrieve_json_payload: bool,
|
|
113
|
+
logged_in_user: str,
|
|
114
|
+
):
|
|
115
|
+
report_query = api.Query(
|
|
116
|
+
type=api.QueryType.search,
|
|
117
|
+
space_name=space_name,
|
|
118
|
+
subpath="/",
|
|
119
|
+
search=f"@schema_shortname:{'|'.join(schema_names)}",
|
|
120
|
+
filter_schema_names=["meta"],
|
|
121
|
+
sort_by="updated_at",
|
|
122
|
+
sort_type=api.SortType.descending,
|
|
123
|
+
limit=50, # to be in safe side if the query filtered out some invalid entries
|
|
124
|
+
retrieve_json_payload=retrieve_json_payload,
|
|
125
|
+
)
|
|
126
|
+
_, records = await serve_query(report_query, logged_in_user)
|
|
127
|
+
|
|
128
|
+
return records[0] if records else None
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
async def get_resource_obj_or_none(
|
|
132
|
+
*,
|
|
133
|
+
space_name: str,
|
|
134
|
+
subpath: str,
|
|
135
|
+
shortname: str,
|
|
136
|
+
resource_type: str,
|
|
137
|
+
user_shortname: str,
|
|
138
|
+
):
|
|
139
|
+
resource_cls = getattr(
|
|
140
|
+
sys.modules["models.core"], camel_case(resource_type))
|
|
141
|
+
try:
|
|
142
|
+
return await db.load(
|
|
143
|
+
space_name=space_name,
|
|
144
|
+
subpath=subpath,
|
|
145
|
+
shortname=shortname,
|
|
146
|
+
class_type=resource_cls,
|
|
147
|
+
user_shortname=user_shortname,
|
|
148
|
+
)
|
|
149
|
+
except Exception:
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
async def get_payload_obj_or_none(
|
|
154
|
+
*,
|
|
155
|
+
space_name: str,
|
|
156
|
+
subpath: str,
|
|
157
|
+
filename: str,
|
|
158
|
+
resource_type: str,
|
|
159
|
+
):
|
|
160
|
+
resource_cls = getattr(
|
|
161
|
+
sys.modules["models.core"], camel_case(resource_type))
|
|
162
|
+
try:
|
|
163
|
+
return await db.load_resource_payload(
|
|
164
|
+
space_name=space_name,
|
|
165
|
+
subpath=subpath,
|
|
166
|
+
filename=filename,
|
|
167
|
+
class_type=resource_cls,
|
|
168
|
+
)
|
|
169
|
+
except Exception:
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
async def folder_meta_content_check(
|
|
174
|
+
space_name, subpath, folder_name, spaces_path_parts,
|
|
175
|
+
user_shortname, folder_name_index, invalid_folders
|
|
176
|
+
):
|
|
177
|
+
try:
|
|
178
|
+
folder_meta_content = await db.load(
|
|
179
|
+
space_name=space_name,
|
|
180
|
+
subpath=folder_name,
|
|
181
|
+
shortname="",
|
|
182
|
+
class_type=core.Folder,
|
|
183
|
+
user_shortname=user_shortname,
|
|
184
|
+
)
|
|
185
|
+
if (
|
|
186
|
+
folder_meta_content.payload
|
|
187
|
+
and folder_meta_content.payload.content_type == ContentType.json
|
|
188
|
+
):
|
|
189
|
+
payload_path = "/"
|
|
190
|
+
subpath_parts = subpath.split("/")
|
|
191
|
+
if len(subpath_parts) > (len(spaces_path_parts) + 2):
|
|
192
|
+
payload_path = "/".join(
|
|
193
|
+
subpath_parts[folder_name_index:-1])
|
|
194
|
+
folder_meta_payload = await db.load_resource_payload(
|
|
195
|
+
space_name,
|
|
196
|
+
payload_path,
|
|
197
|
+
str(folder_meta_content.payload.body),
|
|
198
|
+
core.Folder,
|
|
199
|
+
)
|
|
200
|
+
if folder_meta_content.payload.schema_shortname and folder_meta_payload:
|
|
201
|
+
await db.validate_payload_with_schema(
|
|
202
|
+
payload_data=folder_meta_payload,
|
|
203
|
+
space_name=space_name,
|
|
204
|
+
schema_shortname=folder_meta_content.payload.schema_shortname,
|
|
205
|
+
)
|
|
206
|
+
except Exception:
|
|
207
|
+
invalid_folders.append(folder_name)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
async def health_check_entry_vsd(
|
|
211
|
+
space_name, folder_name, entry_shortname, entry_resource_type, user_shortname,
|
|
212
|
+
folder, folders_report, max_invalid_size, entry_meta_obj
|
|
213
|
+
):
|
|
214
|
+
await health_check_entry(
|
|
215
|
+
space_name=space_name,
|
|
216
|
+
subpath=folder_name,
|
|
217
|
+
shortname=entry_shortname,
|
|
218
|
+
resource_type=entry_resource_type,
|
|
219
|
+
user_shortname=user_shortname,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# VALIDATE ENTRY ATTACHMENTS
|
|
223
|
+
attachments_path = f"{folder.path}/{entry_shortname}"
|
|
224
|
+
attachment_folders = os.scandir(attachments_path)
|
|
225
|
+
for attachment_folder in attachment_folders:
|
|
226
|
+
# i.e. attachment_folder = attachments.media
|
|
227
|
+
if attachment_folder.is_file():
|
|
228
|
+
continue
|
|
229
|
+
|
|
230
|
+
attachment_folder_files = os.scandir(attachment_folder)
|
|
231
|
+
for attachment_folder_file in attachment_folder_files:
|
|
232
|
+
# i.e. attachment_folder_file = meta.*.json or *.png
|
|
233
|
+
if (
|
|
234
|
+
not attachment_folder_file.is_file()
|
|
235
|
+
or not os.access(attachment_folder_file.path, os.W_OK)
|
|
236
|
+
or not os.access(attachment_folder_file.path, os.R_OK)
|
|
237
|
+
):
|
|
238
|
+
raise Exception(
|
|
239
|
+
f"can't access this attachment {attachment_folder_file.path[len(str(settings.spaces_folder)):]}"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
attachment_match = regex.ATTACHMENT_PATTERN.search(attachment_folder_file.path)
|
|
243
|
+
if not attachment_match:
|
|
244
|
+
# if it's the media file not its meta json file
|
|
245
|
+
continue
|
|
246
|
+
attachment_shortname = attachment_match.group(2)
|
|
247
|
+
attachment_resource_type = attachment_match.group(1)
|
|
248
|
+
await health_check_entry(
|
|
249
|
+
space_name=space_name,
|
|
250
|
+
subpath=f"{folder_name}/{entry_shortname}",
|
|
251
|
+
shortname=attachment_shortname,
|
|
252
|
+
resource_type=attachment_resource_type,
|
|
253
|
+
user_shortname=user_shortname,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
if "valid_entries" not in folders_report[folder_name]:
|
|
257
|
+
folders_report[folder_name]["valid_entries"] = 1
|
|
258
|
+
else:
|
|
259
|
+
folders_report[folder_name]["valid_entries"] += 1
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
async def validate_subpath_data(
|
|
263
|
+
space_name: str,
|
|
264
|
+
subpath: str,
|
|
265
|
+
user_shortname: str,
|
|
266
|
+
invalid_folders: list[str],
|
|
267
|
+
folders_report: dict[str, dict[str, Any]],
|
|
268
|
+
meta_folders_health: list[str],
|
|
269
|
+
max_invalid_size: int,
|
|
270
|
+
):
|
|
271
|
+
"""
|
|
272
|
+
Params:
|
|
273
|
+
@subpath: str holding the full path, ex: ../spaces/aftersales/reports
|
|
274
|
+
|
|
275
|
+
Algorithm:
|
|
276
|
+
- if subpath ends with .dm return
|
|
277
|
+
- for folder in scandir(subpath)
|
|
278
|
+
- if folder ends with .dm
|
|
279
|
+
- get folder_meta = folder/meta.folder.json
|
|
280
|
+
- validate folder_meta
|
|
281
|
+
- loop over folder.entries and validate them along with theire attachments
|
|
282
|
+
- else
|
|
283
|
+
- call myself with subpath = folder
|
|
284
|
+
"""
|
|
285
|
+
spaces_path_parts = str(settings.spaces_folder).split("/")
|
|
286
|
+
folder_name_index = len(spaces_path_parts) + 1
|
|
287
|
+
|
|
288
|
+
if subpath.endswith(".dm"):
|
|
289
|
+
return
|
|
290
|
+
|
|
291
|
+
subpath_folders = os.scandir(subpath)
|
|
292
|
+
for folder in subpath_folders:
|
|
293
|
+
if not folder.is_dir():
|
|
294
|
+
continue
|
|
295
|
+
|
|
296
|
+
if folder.name != ".dm":
|
|
297
|
+
await validate_subpath_data(
|
|
298
|
+
space_name,
|
|
299
|
+
folder.path,
|
|
300
|
+
user_shortname,
|
|
301
|
+
invalid_folders,
|
|
302
|
+
folders_report,
|
|
303
|
+
meta_folders_health,
|
|
304
|
+
max_invalid_size,
|
|
305
|
+
)
|
|
306
|
+
continue
|
|
307
|
+
|
|
308
|
+
folder_meta = Path(f"{folder.path}/meta.folder.json")
|
|
309
|
+
folder_name = "/".join(subpath.split("/")[folder_name_index:])
|
|
310
|
+
if not folder_meta.is_file():
|
|
311
|
+
meta_folders_health.append(
|
|
312
|
+
str(folder_meta)[len(str(settings.spaces_folder)):]
|
|
313
|
+
)
|
|
314
|
+
continue
|
|
315
|
+
|
|
316
|
+
await folder_meta_content_check(
|
|
317
|
+
space_name, subpath, folder_name, spaces_path_parts,
|
|
318
|
+
user_shortname, folder_name_index, invalid_folders
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
folders_report.setdefault(folder_name, {})
|
|
322
|
+
|
|
323
|
+
# VALIDATE FOLDER ENTRIES
|
|
324
|
+
folder_entries = os.scandir(folder.path)
|
|
325
|
+
for entry in folder_entries:
|
|
326
|
+
if entry.is_file():
|
|
327
|
+
continue
|
|
328
|
+
|
|
329
|
+
entry_files = os.scandir(entry)
|
|
330
|
+
entry_match = None
|
|
331
|
+
for file in entry_files:
|
|
332
|
+
if file.is_dir():
|
|
333
|
+
continue
|
|
334
|
+
entry_match = regex.FILE_PATTERN.search(file.path)
|
|
335
|
+
|
|
336
|
+
if entry_match:
|
|
337
|
+
break
|
|
338
|
+
|
|
339
|
+
if not entry_match:
|
|
340
|
+
issue = {
|
|
341
|
+
"issues": ["meta"],
|
|
342
|
+
"uuid": "",
|
|
343
|
+
"shortname": entry.name,
|
|
344
|
+
"exception": f"Can't access this meta {subpath[len(str(settings.spaces_folder)):]}/{entry.name}",
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if "invalid_entries" not in folders_report[folder_name]:
|
|
348
|
+
folders_report[folder_name]["invalid_entries"] = [issue]
|
|
349
|
+
else:
|
|
350
|
+
if (
|
|
351
|
+
len(folders_report[folder_name]["invalid_entries"])
|
|
352
|
+
>= max_invalid_size
|
|
353
|
+
):
|
|
354
|
+
break
|
|
355
|
+
folders_report[folder_name]["invalid_entries"].append(
|
|
356
|
+
issue)
|
|
357
|
+
continue
|
|
358
|
+
|
|
359
|
+
entry_shortname = entry_match.group(1)
|
|
360
|
+
entry_resource_type = entry_match.group(2)
|
|
361
|
+
|
|
362
|
+
if folder_name == "schema" and entry_shortname == "meta_schema":
|
|
363
|
+
folders_report[folder_name].setdefault("valid_entries", 0)
|
|
364
|
+
folders_report[folder_name]["valid_entries"] += 1
|
|
365
|
+
continue
|
|
366
|
+
|
|
367
|
+
entry_meta_obj = None
|
|
368
|
+
try:
|
|
369
|
+
await health_check_entry_vsd(
|
|
370
|
+
space_name, folder_name, entry_shortname, entry_resource_type, user_shortname,
|
|
371
|
+
folder, folders_report, max_invalid_size, entry_meta_obj
|
|
372
|
+
)
|
|
373
|
+
except Exception as e:
|
|
374
|
+
issue_type = "payload"
|
|
375
|
+
uuid = ""
|
|
376
|
+
if not entry_meta_obj:
|
|
377
|
+
issue_type = "meta"
|
|
378
|
+
else:
|
|
379
|
+
uuid = str(
|
|
380
|
+
entry_meta_obj.uuid) if entry_meta_obj.uuid else ""
|
|
381
|
+
|
|
382
|
+
issue = {
|
|
383
|
+
"issues": [issue_type],
|
|
384
|
+
"uuid": uuid,
|
|
385
|
+
"shortname": entry_shortname,
|
|
386
|
+
"resource_type": entry_resource_type,
|
|
387
|
+
"exception": str(e),
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if "invalid_entries" not in folders_report[folder_name]:
|
|
391
|
+
folders_report[folder_name]["invalid_entries"] = [issue]
|
|
392
|
+
else:
|
|
393
|
+
if (
|
|
394
|
+
len(folders_report[folder_name]["invalid_entries"])
|
|
395
|
+
>= max_invalid_size
|
|
396
|
+
):
|
|
397
|
+
break
|
|
398
|
+
folders_report[folder_name]["invalid_entries"].append(
|
|
399
|
+
issue
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
if not folders_report.get(folder_name, {}):
|
|
403
|
+
del folders_report[folder_name]
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
async def health_check_entry(
|
|
407
|
+
space_name: str,
|
|
408
|
+
subpath: str,
|
|
409
|
+
resource_type: str,
|
|
410
|
+
shortname: str,
|
|
411
|
+
user_shortname: str,
|
|
412
|
+
):
|
|
413
|
+
resource_class = getattr(
|
|
414
|
+
sys.modules["models.core"], camel_case(resource_type)
|
|
415
|
+
)
|
|
416
|
+
entry_meta_obj = resource_class.model_validate(await db.load(
|
|
417
|
+
space_name=space_name,
|
|
418
|
+
subpath=subpath,
|
|
419
|
+
shortname=shortname,
|
|
420
|
+
class_type=resource_class,
|
|
421
|
+
user_shortname=user_shortname,
|
|
422
|
+
))
|
|
423
|
+
if entry_meta_obj.shortname != shortname:
|
|
424
|
+
raise Exception(
|
|
425
|
+
"the shortname which got from the folder path doesn't match the shortname in the meta file."
|
|
426
|
+
)
|
|
427
|
+
payload_file_path = None
|
|
428
|
+
if (
|
|
429
|
+
entry_meta_obj.payload
|
|
430
|
+
and entry_meta_obj.payload.content_type == ContentType.image
|
|
431
|
+
):
|
|
432
|
+
payload_file_path = Path(f"{subpath}/{entry_meta_obj.payload.body}")
|
|
433
|
+
if (
|
|
434
|
+
not payload_file_path.is_file()
|
|
435
|
+
or not bool(
|
|
436
|
+
re.match(
|
|
437
|
+
regex.IMG_EXT,
|
|
438
|
+
entry_meta_obj.payload.body.split(".")[-1],
|
|
439
|
+
)
|
|
440
|
+
)
|
|
441
|
+
or not os.access(payload_file_path, os.R_OK)
|
|
442
|
+
or not os.access(payload_file_path, os.W_OK)
|
|
443
|
+
):
|
|
444
|
+
if payload_file_path:
|
|
445
|
+
raise Exception(
|
|
446
|
+
f"can't access this payload {payload_file_path}"
|
|
447
|
+
)
|
|
448
|
+
else:
|
|
449
|
+
raise Exception(
|
|
450
|
+
f"can't access this payload {subpath}"
|
|
451
|
+
f"/{entry_meta_obj.shortname}"
|
|
452
|
+
)
|
|
453
|
+
elif (
|
|
454
|
+
entry_meta_obj.payload
|
|
455
|
+
and isinstance(entry_meta_obj.payload.body, str)
|
|
456
|
+
and entry_meta_obj.payload.content_type == ContentType.json
|
|
457
|
+
):
|
|
458
|
+
payload_file_path = db.payload_path(space_name, subpath, resource_class)
|
|
459
|
+
if not entry_meta_obj.payload.body.endswith(
|
|
460
|
+
".json"
|
|
461
|
+
) or not os.access(payload_file_path, os.W_OK):
|
|
462
|
+
raise Exception(
|
|
463
|
+
f"can't access this payload {payload_file_path}"
|
|
464
|
+
)
|
|
465
|
+
payload_file_content = await db.load_resource_payload(
|
|
466
|
+
space_name,
|
|
467
|
+
subpath,
|
|
468
|
+
entry_meta_obj.payload.body,
|
|
469
|
+
resource_class,
|
|
470
|
+
)
|
|
471
|
+
if entry_meta_obj.payload.schema_shortname and payload_file_content:
|
|
472
|
+
await db.validate_payload_with_schema(
|
|
473
|
+
payload_data=payload_file_content,
|
|
474
|
+
space_name=space_name,
|
|
475
|
+
schema_shortname=entry_meta_obj.payload.schema_shortname,
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
if (
|
|
479
|
+
entry_meta_obj.payload.checksum and
|
|
480
|
+
entry_meta_obj.payload.client_checksum and
|
|
481
|
+
entry_meta_obj.payload.checksum != entry_meta_obj.payload.client_checksum
|
|
482
|
+
):
|
|
483
|
+
raise Exception(
|
|
484
|
+
f"payload.checksum not equal payload.client_checksum {subpath}/{entry_meta_obj.shortname}"
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
async def url_shortner(url: str) -> str:
|
|
488
|
+
token_uuid = str(uuid4())[:8]
|
|
489
|
+
await db.set_url_shortner(token_uuid, url)
|
|
490
|
+
return f"{settings.public_app_url}/managed/s/{token_uuid}"
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
async def store_user_invitation_token(user: core.User, channel: str) -> str | None:
|
|
494
|
+
"""Generate and Store an invitation token
|
|
495
|
+
|
|
496
|
+
Returns:
|
|
497
|
+
invitation link or None if the user is not eligible
|
|
498
|
+
"""
|
|
499
|
+
invitation_value = None
|
|
500
|
+
if channel == "SMS" and user.msisdn:
|
|
501
|
+
invitation_value = f"{channel}:{user.msisdn}"
|
|
502
|
+
elif channel == "EMAIL" and user.email:
|
|
503
|
+
invitation_value = f"{channel}:{user.email}"
|
|
504
|
+
|
|
505
|
+
if not invitation_value:
|
|
506
|
+
return None
|
|
507
|
+
|
|
508
|
+
invitation_token = generate_jwt(
|
|
509
|
+
{"shortname": user.shortname, "channel": channel},
|
|
510
|
+
settings.jwt_access_expires,
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
await db.set_invitation(invitation_token, invitation_value)
|
|
514
|
+
|
|
515
|
+
return core.User.invitation_url_template() \
|
|
516
|
+
.replace("{url}", settings.invitation_link) \
|
|
517
|
+
.replace("{token}", invitation_token) \
|
|
518
|
+
.replace("{lang}", Language.code(user.language)) \
|
|
519
|
+
.replace("{user_type}", user.type)
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
async def delete_space(space_name, record, owner_shortname):
|
|
523
|
+
if settings.active_data_db == "sql":
|
|
524
|
+
resource_obj = core.Meta.from_record(
|
|
525
|
+
record=record, owner_shortname=owner_shortname
|
|
526
|
+
)
|
|
527
|
+
await db.delete(space_name, record.subpath, resource_obj, owner_shortname)
|
|
528
|
+
|
|
529
|
+
os.system(f"rm -r {settings.spaces_folder}/{space_name}")
|
utils/router_helper.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from utils.internal_error_code import InternalErrorCode
|
|
2
|
+
from data_adapters.adapter import data_adapter as db
|
|
3
|
+
import models.api as api
|
|
4
|
+
from fastapi import status
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
async def is_space_exist(space_name, should_exist=True):
|
|
8
|
+
|
|
9
|
+
space = await db.fetch_space(space_name)
|
|
10
|
+
|
|
11
|
+
if (space is not None) ^ should_exist:
|
|
12
|
+
raise api.Exception(
|
|
13
|
+
status.HTTP_400_BAD_REQUEST,
|
|
14
|
+
api.Error(
|
|
15
|
+
type="request",
|
|
16
|
+
code=InternalErrorCode.INVALID_SPACE_NAME,
|
|
17
|
+
message=f"Space name {space_name} provided is empty or invalid [3]",
|
|
18
|
+
),
|
|
19
|
+
)
|