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