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
|
@@ -0,0 +1,1013 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from fastapi import status
|
|
8
|
+
from fastapi.encoders import jsonable_encoder
|
|
9
|
+
import aiofiles
|
|
10
|
+
from data_adapters.file.redis_services import RedisServices
|
|
11
|
+
from models import core, api
|
|
12
|
+
from utils import regex
|
|
13
|
+
|
|
14
|
+
from utils.helpers import camel_case, alter_dict_keys, str_to_datetime, flatten_all
|
|
15
|
+
from utils.internal_error_code import InternalErrorCode
|
|
16
|
+
from utils.query_policies_helper import get_user_query_policies
|
|
17
|
+
from utils.settings import settings
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def redis_query_search(
|
|
21
|
+
query: api.Query, user_shortname: str, redis_query_policies: list = []
|
|
22
|
+
) -> tuple:
|
|
23
|
+
search_res: list = []
|
|
24
|
+
total = 0
|
|
25
|
+
|
|
26
|
+
if not query.filter_schema_names:
|
|
27
|
+
query.filter_schema_names = ["meta"]
|
|
28
|
+
|
|
29
|
+
created_at_search = ""
|
|
30
|
+
|
|
31
|
+
if query.from_date and query.to_date:
|
|
32
|
+
created_at_search = (
|
|
33
|
+
"[" + f"{query.from_date.timestamp()} {query.to_date.timestamp()}" + "]"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
elif query.from_date:
|
|
37
|
+
created_at_search = (
|
|
38
|
+
"["
|
|
39
|
+
+ f"{query.from_date.timestamp()} {datetime(2199, 12, 31).timestamp()}"
|
|
40
|
+
+ "]"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
elif query.to_date:
|
|
44
|
+
created_at_search = (
|
|
45
|
+
"["
|
|
46
|
+
+ f"{datetime(2010, 1, 1).timestamp()} {query.to_date.timestamp()}"
|
|
47
|
+
+ "]"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
limit = query.limit
|
|
51
|
+
offset = query.offset
|
|
52
|
+
if len(query.filter_schema_names) > 1 and query.sort_by:
|
|
53
|
+
limit += offset
|
|
54
|
+
offset = 0
|
|
55
|
+
|
|
56
|
+
async with RedisServices() as redis_services:
|
|
57
|
+
for schema_name in query.filter_schema_names:
|
|
58
|
+
redis_res = await redis_services.search(
|
|
59
|
+
space_name=query.space_name,
|
|
60
|
+
schema_name=schema_name,
|
|
61
|
+
search=str(query.search),
|
|
62
|
+
filters={
|
|
63
|
+
"resource_type": query.filter_types or [],
|
|
64
|
+
"shortname": query.filter_shortnames or [],
|
|
65
|
+
"tags": query.filter_tags or [],
|
|
66
|
+
"subpath": [query.subpath],
|
|
67
|
+
"query_policies": redis_query_policies,
|
|
68
|
+
"user_shortname": user_shortname,
|
|
69
|
+
"created_at": created_at_search,
|
|
70
|
+
},
|
|
71
|
+
exact_subpath=query.exact_subpath,
|
|
72
|
+
limit=limit,
|
|
73
|
+
offset=offset,
|
|
74
|
+
highlight_fields=list(query.highlight_fields.keys()),
|
|
75
|
+
sort_by=query.sort_by,
|
|
76
|
+
sort_type=query.sort_type or api.SortType.ascending,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if redis_res:
|
|
80
|
+
search_res.extend(redis_res["data"])
|
|
81
|
+
total += redis_res["total"]
|
|
82
|
+
return search_res, total
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
async def get_record_from_redis_doc(
|
|
86
|
+
db,
|
|
87
|
+
space_name: str,
|
|
88
|
+
doc: dict,
|
|
89
|
+
retrieve_json_payload: bool = False,
|
|
90
|
+
retrieve_attachments: bool = False,
|
|
91
|
+
validate_schema: bool = False,
|
|
92
|
+
filter_types: list | None = None,
|
|
93
|
+
retrieve_lock_status: bool = False,
|
|
94
|
+
) -> core.Record:
|
|
95
|
+
meta_doc_content = {}
|
|
96
|
+
payload_doc_content = {}
|
|
97
|
+
resource_class = getattr(
|
|
98
|
+
sys.modules["models.core"],
|
|
99
|
+
camel_case(doc["resource_type"]),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
for key, value in doc.items():
|
|
103
|
+
if key in resource_class.model_fields.keys():
|
|
104
|
+
meta_doc_content[key] = value
|
|
105
|
+
elif key not in RedisServices.SYS_ATTRIBUTES:
|
|
106
|
+
payload_doc_content[key] = value
|
|
107
|
+
|
|
108
|
+
async with RedisServices() as redis_services:
|
|
109
|
+
# Get payload doc
|
|
110
|
+
if (
|
|
111
|
+
not payload_doc_content
|
|
112
|
+
and retrieve_json_payload
|
|
113
|
+
and "payload_doc_id" in doc
|
|
114
|
+
):
|
|
115
|
+
payload_doc_content = await redis_services.get_payload_doc(
|
|
116
|
+
doc["payload_doc_id"], doc["resource_type"]
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
meta_doc_content["created_at"] = datetime.fromtimestamp(
|
|
120
|
+
meta_doc_content["created_at"]
|
|
121
|
+
)
|
|
122
|
+
meta_doc_content["updated_at"] = datetime.fromtimestamp(
|
|
123
|
+
meta_doc_content["updated_at"]
|
|
124
|
+
)
|
|
125
|
+
resource_obj = resource_class.model_validate(meta_doc_content)
|
|
126
|
+
resource_base_record = resource_obj.to_record(
|
|
127
|
+
doc["subpath"],
|
|
128
|
+
meta_doc_content["shortname"],
|
|
129
|
+
[],
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Get lock data
|
|
133
|
+
if retrieve_lock_status:
|
|
134
|
+
locked_data = await redis_services.get_lock_doc(
|
|
135
|
+
space_name,
|
|
136
|
+
doc["subpath"],
|
|
137
|
+
doc["shortname"],
|
|
138
|
+
)
|
|
139
|
+
if locked_data:
|
|
140
|
+
resource_base_record.attributes["locked"] = locked_data
|
|
141
|
+
|
|
142
|
+
# Get attachments
|
|
143
|
+
entry_path = (
|
|
144
|
+
settings.spaces_folder
|
|
145
|
+
/ f"{space_name}/{doc['subpath']}/.dm/{meta_doc_content['shortname']}"
|
|
146
|
+
)
|
|
147
|
+
if retrieve_attachments and entry_path.is_dir():
|
|
148
|
+
resource_base_record.attachments = await db.get_entry_attachments(
|
|
149
|
+
subpath=f"{doc['subpath']}/{meta_doc_content['shortname']}",
|
|
150
|
+
attachments_path=entry_path,
|
|
151
|
+
filter_types=filter_types,
|
|
152
|
+
retrieve_json_payload=retrieve_json_payload,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if (
|
|
156
|
+
retrieve_json_payload
|
|
157
|
+
and resource_base_record.attributes["payload"]
|
|
158
|
+
and resource_base_record.attributes["payload"].content_type == core.ContentType.json
|
|
159
|
+
):
|
|
160
|
+
resource_base_record.attributes["payload"].body = payload_doc_content
|
|
161
|
+
|
|
162
|
+
# Validate payload
|
|
163
|
+
if (
|
|
164
|
+
retrieve_json_payload
|
|
165
|
+
and resource_obj.payload
|
|
166
|
+
and resource_obj.payload.schema_shortname
|
|
167
|
+
and payload_doc_content is not None
|
|
168
|
+
and validate_schema
|
|
169
|
+
):
|
|
170
|
+
await db.validate_payload_with_schema(
|
|
171
|
+
payload_data=payload_doc_content,
|
|
172
|
+
space_name=space_name,
|
|
173
|
+
schema_shortname=resource_obj.payload.schema_shortname,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
if isinstance(resource_base_record, core.Record):
|
|
177
|
+
return resource_base_record
|
|
178
|
+
else:
|
|
179
|
+
return core.Record()
|
|
180
|
+
|
|
181
|
+
async def redis_query_aggregate(
|
|
182
|
+
query: api.Query,
|
|
183
|
+
redis_query_policies: list = [],
|
|
184
|
+
) -> list:
|
|
185
|
+
if not query.aggregation_data:
|
|
186
|
+
return []
|
|
187
|
+
|
|
188
|
+
if len(query.filter_schema_names) > 1:
|
|
189
|
+
raise api.Exception(
|
|
190
|
+
status.HTTP_400_BAD_REQUEST,
|
|
191
|
+
error=api.Error(
|
|
192
|
+
type="query", code=InternalErrorCode.INVALID_STANDALONE_DATA,
|
|
193
|
+
message="only one argument is allowed in filter_schema_names"
|
|
194
|
+
),
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
created_at_search = ""
|
|
198
|
+
if query.from_date and query.to_date:
|
|
199
|
+
created_at_search = (
|
|
200
|
+
"[" + f"{query.from_date.timestamp()} {query.to_date.timestamp()}" + "]"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
elif query.from_date:
|
|
204
|
+
created_at_search = (
|
|
205
|
+
"["
|
|
206
|
+
+ f"{query.from_date.timestamp()} {datetime(2199, 12, 31).timestamp()}"
|
|
207
|
+
+ "]"
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
elif query.to_date:
|
|
211
|
+
created_at_search = (
|
|
212
|
+
"["
|
|
213
|
+
+ f"{datetime(2010, 1, 1).timestamp()} {query.to_date.timestamp()}"
|
|
214
|
+
+ "]"
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
async with RedisServices() as redis_services:
|
|
218
|
+
value = await redis_services.aggregate(
|
|
219
|
+
space_name=query.space_name,
|
|
220
|
+
schema_name=query.filter_schema_names[0],
|
|
221
|
+
search=str(query.search),
|
|
222
|
+
filters={
|
|
223
|
+
"resource_type": query.filter_types or [],
|
|
224
|
+
"shortname": query.filter_shortnames or [],
|
|
225
|
+
"subpath": [query.subpath],
|
|
226
|
+
"query_policies": redis_query_policies,
|
|
227
|
+
"created_at": created_at_search,
|
|
228
|
+
},
|
|
229
|
+
load=query.aggregation_data.load,
|
|
230
|
+
group_by=query.aggregation_data.group_by,
|
|
231
|
+
reducers=query.aggregation_data.reducers,
|
|
232
|
+
exact_subpath=query.exact_subpath,
|
|
233
|
+
sort_by=query.sort_by,
|
|
234
|
+
max=query.limit,
|
|
235
|
+
sort_type=query.sort_type or api.SortType.ascending,
|
|
236
|
+
)
|
|
237
|
+
if isinstance(value, list):
|
|
238
|
+
return value
|
|
239
|
+
else:
|
|
240
|
+
return []
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
async def serve_query_space(db, query, logged_in_user):
|
|
244
|
+
records = []
|
|
245
|
+
total = 0
|
|
246
|
+
|
|
247
|
+
spaces = await db.get_spaces()
|
|
248
|
+
for space_json in spaces.values():
|
|
249
|
+
space = core.Space.model_validate_json(space_json)
|
|
250
|
+
from utils.access_control import access_control
|
|
251
|
+
if await access_control.check_space_access(
|
|
252
|
+
logged_in_user, space.shortname
|
|
253
|
+
):
|
|
254
|
+
total += 1
|
|
255
|
+
records.append(
|
|
256
|
+
space.to_record(
|
|
257
|
+
query.subpath,
|
|
258
|
+
space.shortname,
|
|
259
|
+
query.include_fields if query.include_fields else [],
|
|
260
|
+
)
|
|
261
|
+
)
|
|
262
|
+
if not query.sort_by:
|
|
263
|
+
query.sort_by = "ordinal"
|
|
264
|
+
if records:
|
|
265
|
+
record_fields = list(records[0].model_fields.keys())
|
|
266
|
+
records = sorted(
|
|
267
|
+
records,
|
|
268
|
+
key=lambda d: d.__getattribute__(query.sort_by)
|
|
269
|
+
if query.sort_by in record_fields
|
|
270
|
+
else d.attributes[query.sort_by]
|
|
271
|
+
if query.sort_by in d.attributes and d.attributes[query.sort_by] is not None
|
|
272
|
+
else 1,
|
|
273
|
+
reverse=(query.sort_type == api.SortType.descending),
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
return total, records
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
async def serve_query_search(db, query, logged_in_user):
|
|
280
|
+
records = []
|
|
281
|
+
total = 0
|
|
282
|
+
|
|
283
|
+
redis_query_policies = await get_user_query_policies(
|
|
284
|
+
db, logged_in_user, query.space_name, query.subpath
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
search_res, total = await redis_query_search(query, logged_in_user, redis_query_policies)
|
|
288
|
+
res_data = []
|
|
289
|
+
for redis_document in search_res:
|
|
290
|
+
res_data.append(json.loads(redis_document))
|
|
291
|
+
if len(query.filter_schema_names) > 1:
|
|
292
|
+
if query.sort_by:
|
|
293
|
+
res_data = sorted(
|
|
294
|
+
res_data,
|
|
295
|
+
key=lambda d: d[query.sort_by]
|
|
296
|
+
if query.sort_by in d
|
|
297
|
+
else d.get("payload", {})[query.sort_by]
|
|
298
|
+
if query.sort_by in d.get("payload", {})
|
|
299
|
+
else "",
|
|
300
|
+
reverse=(query.sort_type == api.SortType.descending),
|
|
301
|
+
)
|
|
302
|
+
res_data = res_data[query.offset: (query.limit + query.offset)]
|
|
303
|
+
|
|
304
|
+
for redis_doc_dict in res_data:
|
|
305
|
+
try:
|
|
306
|
+
resource_base_record = await get_record_from_redis_doc(
|
|
307
|
+
db,
|
|
308
|
+
space_name=query.space_name,
|
|
309
|
+
doc=redis_doc_dict,
|
|
310
|
+
retrieve_json_payload=query.retrieve_json_payload,
|
|
311
|
+
retrieve_attachments=query.retrieve_attachments,
|
|
312
|
+
validate_schema=query.validate_schema,
|
|
313
|
+
filter_types=query.filter_types,
|
|
314
|
+
retrieve_lock_status=query.retrieve_lock_status,
|
|
315
|
+
)
|
|
316
|
+
except Exception as e:
|
|
317
|
+
print("Error in get_record_from_redis_doc", e)
|
|
318
|
+
continue
|
|
319
|
+
|
|
320
|
+
# Don't repeat the same entry comming from different indices
|
|
321
|
+
# if resource_base_record in records:
|
|
322
|
+
# total -= 1
|
|
323
|
+
# continue
|
|
324
|
+
|
|
325
|
+
if query.highlight_fields:
|
|
326
|
+
for key, value in query.highlight_fields.items():
|
|
327
|
+
resource_base_record.attributes[value] = getattr(
|
|
328
|
+
redis_doc_dict, key, None
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
resource_base_record.attributes = alter_dict_keys(
|
|
332
|
+
jsonable_encoder(resource_base_record.attributes, exclude_none=True),
|
|
333
|
+
query.include_fields,
|
|
334
|
+
query.exclude_fields,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
records.append(resource_base_record)
|
|
338
|
+
|
|
339
|
+
return total, records
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
async def serve_query_subpath_sorting(query, records):
|
|
343
|
+
sort_reverse = (
|
|
344
|
+
query.sort_type is not None
|
|
345
|
+
and query.sort_type == api.SortType.descending
|
|
346
|
+
)
|
|
347
|
+
if query.sort_by in core.Record.model_fields:
|
|
348
|
+
records = sorted(
|
|
349
|
+
records,
|
|
350
|
+
key=lambda record: record.__getattribute__(
|
|
351
|
+
str(query.sort_by)),
|
|
352
|
+
reverse=sort_reverse,
|
|
353
|
+
)
|
|
354
|
+
else:
|
|
355
|
+
records = sorted(
|
|
356
|
+
records,
|
|
357
|
+
key=lambda record: record.attributes[str(
|
|
358
|
+
query.sort_by)],
|
|
359
|
+
reverse=sort_reverse,
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
return records
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
async def serve_query_subpath_check_payload(db, resource_base_record, path, resource_obj, query):
|
|
366
|
+
payload_body = resource_base_record.attributes[
|
|
367
|
+
"payload"
|
|
368
|
+
].body
|
|
369
|
+
if not payload_body or isinstance(payload_body, str):
|
|
370
|
+
async with aiofiles.open(
|
|
371
|
+
path / resource_obj.payload.body, "r"
|
|
372
|
+
) as payload_file_content:
|
|
373
|
+
payload_body = json.loads(
|
|
374
|
+
await payload_file_content.read()
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
if query.validate_schema:
|
|
378
|
+
await db.validate_payload_with_schema(
|
|
379
|
+
payload_data=payload_body,
|
|
380
|
+
space_name=query.space_name,
|
|
381
|
+
schema_shortname=resource_obj.payload.schema_shortname,
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
async def set_attachment_for_payload(db, path, folder_obj, folder_record, query, meta_path, shortname):
|
|
386
|
+
if (
|
|
387
|
+
query.retrieve_json_payload
|
|
388
|
+
and folder_obj.payload
|
|
389
|
+
and folder_obj.payload.content_type
|
|
390
|
+
and folder_obj.payload.content_type == core.ContentType.json
|
|
391
|
+
and isinstance(folder_obj.payload.body, str)
|
|
392
|
+
and (path / folder_obj.payload.body).is_file()
|
|
393
|
+
):
|
|
394
|
+
async with aiofiles.open(
|
|
395
|
+
path / folder_obj.payload.body, "r"
|
|
396
|
+
) as payload_file_content:
|
|
397
|
+
folder_record.attributes["payload"].body = json.loads(
|
|
398
|
+
await payload_file_content.read()
|
|
399
|
+
)
|
|
400
|
+
if os.path.exists(meta_path / shortname):
|
|
401
|
+
folder_record.attachments = await db.get_entry_attachments(
|
|
402
|
+
subpath=f"{query.subpath if query.subpath != '/' else ''}/{shortname}",
|
|
403
|
+
attachments_path=(meta_path / shortname),
|
|
404
|
+
filter_types=query.filter_types,
|
|
405
|
+
include_fields=query.include_fields,
|
|
406
|
+
retrieve_json_payload=query.retrieve_json_payload,
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
return folder_record
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
async def serve_query_subpath(db, query, logged_in_user):
|
|
413
|
+
records : list[core.Record] = []
|
|
414
|
+
total = 0
|
|
415
|
+
|
|
416
|
+
from utils.access_control import access_control
|
|
417
|
+
|
|
418
|
+
subpath = query.subpath
|
|
419
|
+
if subpath[0] == "/":
|
|
420
|
+
subpath = "." + subpath
|
|
421
|
+
|
|
422
|
+
path = (
|
|
423
|
+
settings.spaces_folder
|
|
424
|
+
/ query.space_name
|
|
425
|
+
/ subpath
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
if query.include_fields is None:
|
|
429
|
+
query.include_fields = []
|
|
430
|
+
|
|
431
|
+
meta_path = path / ".dm"
|
|
432
|
+
if meta_path.is_dir():
|
|
433
|
+
path_iterator = os.scandir(meta_path)
|
|
434
|
+
async with RedisServices() as redis_services:
|
|
435
|
+
for entry in path_iterator:
|
|
436
|
+
if not entry.is_dir():
|
|
437
|
+
continue
|
|
438
|
+
subpath_iterator = os.scandir(str(entry.path))
|
|
439
|
+
for one in subpath_iterator:
|
|
440
|
+
# for one in path.glob(entries_glob):
|
|
441
|
+
match = regex.FILE_PATTERN.search(str(one.path))
|
|
442
|
+
if not match or not one.is_file():
|
|
443
|
+
continue
|
|
444
|
+
|
|
445
|
+
shortname = match.group(1)
|
|
446
|
+
resource_name = match.group(2).lower()
|
|
447
|
+
if (
|
|
448
|
+
query.filter_types
|
|
449
|
+
and core.ResourceType(resource_name) not in query.filter_types
|
|
450
|
+
):
|
|
451
|
+
continue
|
|
452
|
+
|
|
453
|
+
if (
|
|
454
|
+
query.filter_shortnames
|
|
455
|
+
and shortname not in query.filter_shortnames
|
|
456
|
+
):
|
|
457
|
+
continue
|
|
458
|
+
|
|
459
|
+
resource_class = getattr(
|
|
460
|
+
sys.modules["models.core"], camel_case(
|
|
461
|
+
resource_name)
|
|
462
|
+
)
|
|
463
|
+
async with aiofiles.open(str(one.path), "r") as meta_file:
|
|
464
|
+
resource_obj = resource_class.model_validate_json(
|
|
465
|
+
await meta_file.read()
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
if query.filter_tags and (
|
|
469
|
+
not resource_obj.tags
|
|
470
|
+
or not any(
|
|
471
|
+
item in resource_obj.tags
|
|
472
|
+
for item in query.filter_tags
|
|
473
|
+
)
|
|
474
|
+
):
|
|
475
|
+
continue
|
|
476
|
+
|
|
477
|
+
# apply check access
|
|
478
|
+
if not await access_control.check_access(
|
|
479
|
+
user_shortname=logged_in_user,
|
|
480
|
+
space_name=query.space_name,
|
|
481
|
+
subpath=query.subpath,
|
|
482
|
+
resource_type=core.ResourceType(resource_name),
|
|
483
|
+
action_type=core.ActionType.view,
|
|
484
|
+
resource_is_active=resource_obj.is_active,
|
|
485
|
+
resource_owner_shortname=resource_obj.owner_shortname,
|
|
486
|
+
resource_owner_group=resource_obj.owner_group_shortname,
|
|
487
|
+
entry_shortname=shortname,
|
|
488
|
+
):
|
|
489
|
+
continue
|
|
490
|
+
|
|
491
|
+
total += 1
|
|
492
|
+
if len(records) >= query.limit or total < query.offset:
|
|
493
|
+
continue
|
|
494
|
+
|
|
495
|
+
resource_base_record : core.Record = resource_obj.to_record(
|
|
496
|
+
query.subpath,
|
|
497
|
+
shortname,
|
|
498
|
+
query.include_fields,
|
|
499
|
+
)
|
|
500
|
+
if query.retrieve_lock_status and resource_base_record:
|
|
501
|
+
locked_data = await redis_services.get_lock_doc(
|
|
502
|
+
query.space_name,
|
|
503
|
+
query.subpath,
|
|
504
|
+
resource_obj.shortname,
|
|
505
|
+
)
|
|
506
|
+
if locked_data:
|
|
507
|
+
resource_base_record.attributes[
|
|
508
|
+
"locked"
|
|
509
|
+
] = locked_data
|
|
510
|
+
|
|
511
|
+
if (
|
|
512
|
+
query.retrieve_json_payload
|
|
513
|
+
and resource_obj.payload
|
|
514
|
+
and resource_obj.payload.content_type
|
|
515
|
+
and resource_obj.payload.content_type
|
|
516
|
+
== core.ContentType.json
|
|
517
|
+
and (path / resource_obj.payload.body).is_file()
|
|
518
|
+
):
|
|
519
|
+
async with aiofiles.open(
|
|
520
|
+
path / resource_obj.payload.body, "r"
|
|
521
|
+
) as payload_file_content:
|
|
522
|
+
resource_base_record.attributes[
|
|
523
|
+
"payload"
|
|
524
|
+
].body = json.loads(
|
|
525
|
+
await payload_file_content.read()
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
if (
|
|
529
|
+
resource_obj.payload
|
|
530
|
+
and resource_obj.payload.schema_shortname
|
|
531
|
+
):
|
|
532
|
+
try:
|
|
533
|
+
await serve_query_subpath_check_payload(db, resource_base_record, path, resource_obj, query)
|
|
534
|
+
except Exception:
|
|
535
|
+
continue
|
|
536
|
+
|
|
537
|
+
resource_base_record.attachments = (
|
|
538
|
+
await db.get_entry_attachments(
|
|
539
|
+
subpath=f"{query.subpath}/{shortname}",
|
|
540
|
+
attachments_path=(meta_path / shortname),
|
|
541
|
+
filter_types=query.filter_types,
|
|
542
|
+
include_fields=query.include_fields,
|
|
543
|
+
retrieve_json_payload=query.retrieve_json_payload,
|
|
544
|
+
)
|
|
545
|
+
)
|
|
546
|
+
records.append(resource_base_record)
|
|
547
|
+
|
|
548
|
+
subpath_iterator.close()
|
|
549
|
+
if path_iterator:
|
|
550
|
+
path_iterator.close()
|
|
551
|
+
|
|
552
|
+
# Get all matching sub folders
|
|
553
|
+
# apply check access
|
|
554
|
+
|
|
555
|
+
if meta_path.is_dir():
|
|
556
|
+
subfolders_iterator = os.scandir(path)
|
|
557
|
+
for one in subfolders_iterator:
|
|
558
|
+
if not one.is_dir():
|
|
559
|
+
continue
|
|
560
|
+
|
|
561
|
+
subfolder_meta = Path(one.path + "/.dm/meta.folder.json")
|
|
562
|
+
|
|
563
|
+
match = regex.FOLDER_PATTERN.search(str(subfolder_meta))
|
|
564
|
+
|
|
565
|
+
if not match or not subfolder_meta.is_file():
|
|
566
|
+
continue
|
|
567
|
+
|
|
568
|
+
shortname = match.group(1)
|
|
569
|
+
if not await access_control.check_access(
|
|
570
|
+
user_shortname=logged_in_user,
|
|
571
|
+
space_name=query.space_name,
|
|
572
|
+
subpath=f"{query.subpath}/{shortname}",
|
|
573
|
+
resource_type=core.ResourceType.folder,
|
|
574
|
+
action_type=core.ActionType.query,
|
|
575
|
+
entry_shortname=shortname
|
|
576
|
+
):
|
|
577
|
+
continue
|
|
578
|
+
|
|
579
|
+
if (
|
|
580
|
+
query.filter_shortnames
|
|
581
|
+
and shortname not in query.filter_shortnames
|
|
582
|
+
):
|
|
583
|
+
continue
|
|
584
|
+
|
|
585
|
+
total += 1
|
|
586
|
+
if len(records) >= query.limit or total < query.offset:
|
|
587
|
+
continue
|
|
588
|
+
|
|
589
|
+
folder_obj = core.Folder.model_validate_json(
|
|
590
|
+
subfolder_meta.read_text()
|
|
591
|
+
)
|
|
592
|
+
folder_record = folder_obj.to_record(
|
|
593
|
+
query.subpath,
|
|
594
|
+
shortname,
|
|
595
|
+
query.include_fields,
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
await set_attachment_for_payload(
|
|
599
|
+
db, path, folder_obj, folder_record, query, meta_path, shortname
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
records.append(folder_record)
|
|
603
|
+
|
|
604
|
+
if subfolders_iterator:
|
|
605
|
+
subfolders_iterator.close()
|
|
606
|
+
|
|
607
|
+
if query.sort_by:
|
|
608
|
+
records = await serve_query_subpath_sorting(query, records)
|
|
609
|
+
|
|
610
|
+
return total, records
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
async def serve_query_counters(query, logged_in_user):
|
|
614
|
+
records : list = []
|
|
615
|
+
total = 0
|
|
616
|
+
from utils.access_control import access_control
|
|
617
|
+
if not await access_control.check_access(
|
|
618
|
+
user_shortname=logged_in_user,
|
|
619
|
+
space_name=query.space_name,
|
|
620
|
+
subpath=query.subpath,
|
|
621
|
+
resource_type=core.ResourceType.content,
|
|
622
|
+
action_type=core.ActionType.query
|
|
623
|
+
):
|
|
624
|
+
raise api.Exception(
|
|
625
|
+
status.HTTP_401_UNAUTHORIZED,
|
|
626
|
+
api.Error(
|
|
627
|
+
type="request",
|
|
628
|
+
code=InternalErrorCode.NOT_ALLOWED,
|
|
629
|
+
message="You don't have permission to this action [16]",
|
|
630
|
+
),
|
|
631
|
+
)
|
|
632
|
+
async with RedisServices() as redis_services:
|
|
633
|
+
for schema_name in query.filter_schema_names:
|
|
634
|
+
redis_res = await redis_services.get_count(
|
|
635
|
+
space_name=query.space_name,
|
|
636
|
+
schema_shortname=schema_name,
|
|
637
|
+
)
|
|
638
|
+
total += int(redis_res)
|
|
639
|
+
|
|
640
|
+
return total, records
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
async def serve_query_tags(db, query, user_shortname):
|
|
644
|
+
records = []
|
|
645
|
+
total = 0
|
|
646
|
+
|
|
647
|
+
redis_query_policies = await get_user_query_policies(
|
|
648
|
+
db, user_shortname, query.space_name, query.subpath
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
query.sort_by = "tags"
|
|
652
|
+
query.aggregation_data = api.RedisAggregate(
|
|
653
|
+
group_by=["@tags"],
|
|
654
|
+
reducers=[
|
|
655
|
+
api.RedisReducer(
|
|
656
|
+
reducer_name="count",
|
|
657
|
+
alias="freq"
|
|
658
|
+
)
|
|
659
|
+
]
|
|
660
|
+
)
|
|
661
|
+
rows = await redis_query_aggregate(
|
|
662
|
+
query=query,
|
|
663
|
+
redis_query_policies=redis_query_policies
|
|
664
|
+
)
|
|
665
|
+
records.append(
|
|
666
|
+
core.Record(
|
|
667
|
+
resource_type=core.ResourceType.content,
|
|
668
|
+
shortname="tags_frequency",
|
|
669
|
+
subpath=query.subpath,
|
|
670
|
+
attributes={"result": rows},
|
|
671
|
+
)
|
|
672
|
+
)
|
|
673
|
+
total = 1
|
|
674
|
+
|
|
675
|
+
return total, records
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
async def serve_query_random(db, query, user_shortname):
|
|
679
|
+
records = []
|
|
680
|
+
total = 0
|
|
681
|
+
|
|
682
|
+
redis_query_policies = await get_user_query_policies(
|
|
683
|
+
db, user_shortname, query.space_name, query.subpath
|
|
684
|
+
)
|
|
685
|
+
query.aggregation_data = api.RedisAggregate(
|
|
686
|
+
load=["@__key"],
|
|
687
|
+
group_by=["@resource_type"],
|
|
688
|
+
reducers=[
|
|
689
|
+
api.RedisReducer(
|
|
690
|
+
reducer_name="random_sample",
|
|
691
|
+
alias="id",
|
|
692
|
+
args=["@__key", query.limit]
|
|
693
|
+
)
|
|
694
|
+
]
|
|
695
|
+
)
|
|
696
|
+
async with RedisServices() as redis_services:
|
|
697
|
+
rows = await redis_query_aggregate(
|
|
698
|
+
query=query,
|
|
699
|
+
redis_query_policies=redis_query_policies
|
|
700
|
+
)
|
|
701
|
+
ids = []
|
|
702
|
+
for row in rows:
|
|
703
|
+
ids.extend(row[3])
|
|
704
|
+
docs = await redis_services.get_docs_by_ids(ids)
|
|
705
|
+
total = len(ids)
|
|
706
|
+
for doc in docs:
|
|
707
|
+
doc = doc[0]
|
|
708
|
+
if query.retrieve_json_payload and doc.get("payload_doc_id", None):
|
|
709
|
+
doc["payload"]["body"] = await redis_services.get_payload_doc(
|
|
710
|
+
doc["payload_doc_id"], doc["resource_type"]
|
|
711
|
+
)
|
|
712
|
+
record = core.Record(
|
|
713
|
+
shortname=doc["shortname"],
|
|
714
|
+
resource_type=doc["resource_type"],
|
|
715
|
+
uuid=doc["uuid"],
|
|
716
|
+
subpath=doc["subpath"],
|
|
717
|
+
attributes={"payload": doc.get("payload")},
|
|
718
|
+
)
|
|
719
|
+
entry_path = (
|
|
720
|
+
settings.spaces_folder
|
|
721
|
+
/ f"{query.space_name}/{doc['subpath']}/.dm/{doc['shortname']}"
|
|
722
|
+
)
|
|
723
|
+
if query.retrieve_attachments and entry_path.is_dir():
|
|
724
|
+
record.attachments = await db.get_entry_attachments(
|
|
725
|
+
subpath=f"{doc['subpath']}/{doc['shortname']}",
|
|
726
|
+
attachments_path=entry_path,
|
|
727
|
+
filter_types=query.filter_types,
|
|
728
|
+
include_fields=query.include_fields,
|
|
729
|
+
retrieve_json_payload=query.retrieve_json_payload,
|
|
730
|
+
)
|
|
731
|
+
records.append(record)
|
|
732
|
+
|
|
733
|
+
return total, records
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
async def serve_query_history(query, logged_in_user):
|
|
737
|
+
records = []
|
|
738
|
+
total = 0
|
|
739
|
+
from utils.access_control import access_control
|
|
740
|
+
if not await access_control.check_access(
|
|
741
|
+
user_shortname=logged_in_user,
|
|
742
|
+
space_name=query.space_name,
|
|
743
|
+
subpath=query.subpath,
|
|
744
|
+
resource_type=core.ResourceType.history,
|
|
745
|
+
action_type=core.ActionType.query,
|
|
746
|
+
):
|
|
747
|
+
raise api.Exception(
|
|
748
|
+
status.HTTP_401_UNAUTHORIZED,
|
|
749
|
+
api.Error(
|
|
750
|
+
type="request",
|
|
751
|
+
code=InternalErrorCode.NOT_ALLOWED,
|
|
752
|
+
message="You don't have permission to this action [17]",
|
|
753
|
+
),
|
|
754
|
+
)
|
|
755
|
+
|
|
756
|
+
if not query.filter_shortnames:
|
|
757
|
+
raise api.Exception(
|
|
758
|
+
status.HTTP_400_BAD_REQUEST,
|
|
759
|
+
api.Error(
|
|
760
|
+
type="request",
|
|
761
|
+
code=InternalErrorCode.MISSING_FILTER_SHORTNAMES,
|
|
762
|
+
message="filter_shortnames is missing",
|
|
763
|
+
),
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
path = Path(f"{settings.spaces_folder}/{query.space_name}/"
|
|
767
|
+
f"{query.subpath}/.dm/{query.filter_shortnames[0]}/history.jsonl")
|
|
768
|
+
if path.is_file():
|
|
769
|
+
r1 = subprocess.Popen(
|
|
770
|
+
["tail", "-n", f"+{query.offset}", path], stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
|
771
|
+
)
|
|
772
|
+
rn = subprocess.Popen(
|
|
773
|
+
["sed", "-e", "$a\\\n"], stdin=r1.stdout, stdout=subprocess.PIPE,
|
|
774
|
+
)
|
|
775
|
+
r2 = subprocess.Popen(
|
|
776
|
+
["head", "-n", f"{query.limit}"], stdin=rn.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
|
777
|
+
)
|
|
778
|
+
r3 = subprocess.Popen(
|
|
779
|
+
["tac"], stdin=r2.stdout, stdout=subprocess.PIPE,
|
|
780
|
+
stderr=subprocess.PIPE,
|
|
781
|
+
)
|
|
782
|
+
r4, _ = r3.communicate()
|
|
783
|
+
if r4 is None:
|
|
784
|
+
result = []
|
|
785
|
+
else:
|
|
786
|
+
result = list(
|
|
787
|
+
filter(
|
|
788
|
+
None,
|
|
789
|
+
r4.decode().split("\n"),
|
|
790
|
+
)
|
|
791
|
+
)
|
|
792
|
+
|
|
793
|
+
r, _ = subprocess.Popen(
|
|
794
|
+
f"wc -l {path}".split(" "), stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
|
795
|
+
).communicate()
|
|
796
|
+
|
|
797
|
+
if r is None:
|
|
798
|
+
total = 0
|
|
799
|
+
else:
|
|
800
|
+
total = int(
|
|
801
|
+
r.decode().split()[0],
|
|
802
|
+
10,
|
|
803
|
+
)
|
|
804
|
+
|
|
805
|
+
for line in result:
|
|
806
|
+
action_obj = json.loads(line)
|
|
807
|
+
|
|
808
|
+
records.append(
|
|
809
|
+
core.Record(
|
|
810
|
+
resource_type=core.ResourceType.history,
|
|
811
|
+
shortname=query.filter_shortnames[0],
|
|
812
|
+
subpath=query.subpath,
|
|
813
|
+
attributes=action_obj,
|
|
814
|
+
),
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
return total, records
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
async def serve_query_events(query, logged_in_user):
|
|
821
|
+
records = []
|
|
822
|
+
total = 0
|
|
823
|
+
|
|
824
|
+
trimmed_subpath = query.subpath
|
|
825
|
+
if trimmed_subpath[0] == "/":
|
|
826
|
+
trimmed_subpath = trimmed_subpath[1:]
|
|
827
|
+
|
|
828
|
+
path = Path(
|
|
829
|
+
f"{settings.spaces_folder}/{query.space_name}/.dm/events.jsonl")
|
|
830
|
+
if path.is_file():
|
|
831
|
+
result = []
|
|
832
|
+
if query.search:
|
|
833
|
+
p = subprocess.Popen(
|
|
834
|
+
["grep", f'"{query.search}"', path], stdout=subprocess.PIPE
|
|
835
|
+
)
|
|
836
|
+
p = subprocess.Popen(
|
|
837
|
+
["tail", "-n", f"{query.limit + query.offset}"],
|
|
838
|
+
stdin=p.stdout,
|
|
839
|
+
stdout=subprocess.PIPE,
|
|
840
|
+
)
|
|
841
|
+
p = subprocess.Popen(
|
|
842
|
+
["tac"], stdin=p.stdout, stdout=subprocess.PIPE
|
|
843
|
+
)
|
|
844
|
+
if query.offset > 0:
|
|
845
|
+
p = subprocess.Popen(
|
|
846
|
+
["sed", f"1,{query.offset}d"],
|
|
847
|
+
stdin=p.stdout,
|
|
848
|
+
stdout=subprocess.PIPE,
|
|
849
|
+
)
|
|
850
|
+
r, _ = p.communicate()
|
|
851
|
+
result = list(filter(None, r.decode("utf-8").split("\n")))
|
|
852
|
+
else:
|
|
853
|
+
r1 = subprocess.Popen(
|
|
854
|
+
["tail", "-n", f"{query.limit + query.offset}", path], stdout=subprocess.PIPE,
|
|
855
|
+
)
|
|
856
|
+
|
|
857
|
+
r1 = subprocess.Popen(
|
|
858
|
+
["sed", "-e", "$a\\\n"], stdin=r1.stdout, stdout=subprocess.PIPE,
|
|
859
|
+
)
|
|
860
|
+
if query.offset > 0:
|
|
861
|
+
r1 = subprocess.Popen(
|
|
862
|
+
["head", "-n", f"{query.limit}"], stdin=r1.stdout, stdout=subprocess.PIPE,
|
|
863
|
+
)
|
|
864
|
+
|
|
865
|
+
r6, _ = subprocess.Popen(
|
|
866
|
+
["tac"], stdin=r1.stdout, stdout=subprocess.PIPE,
|
|
867
|
+
).communicate()
|
|
868
|
+
|
|
869
|
+
if r6 is None:
|
|
870
|
+
result = []
|
|
871
|
+
else:
|
|
872
|
+
result = list(
|
|
873
|
+
filter(
|
|
874
|
+
None,
|
|
875
|
+
r6.decode().split("\n"),
|
|
876
|
+
)
|
|
877
|
+
)
|
|
878
|
+
|
|
879
|
+
r, _ = subprocess.Popen(
|
|
880
|
+
f"wc -l {path}".split(" "), stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
|
881
|
+
).communicate()
|
|
882
|
+
|
|
883
|
+
if r is None:
|
|
884
|
+
total = 0
|
|
885
|
+
else:
|
|
886
|
+
total = int(
|
|
887
|
+
r.decode().split()[0],
|
|
888
|
+
10,
|
|
889
|
+
)
|
|
890
|
+
|
|
891
|
+
for line in result:
|
|
892
|
+
action_obj = json.loads(line)
|
|
893
|
+
|
|
894
|
+
if (
|
|
895
|
+
query.from_date
|
|
896
|
+
and str_to_datetime(action_obj["timestamp"]) < query.from_date
|
|
897
|
+
):
|
|
898
|
+
continue
|
|
899
|
+
|
|
900
|
+
if (
|
|
901
|
+
query.to_date
|
|
902
|
+
and str_to_datetime(action_obj["timestamp"]) > query.to_date
|
|
903
|
+
):
|
|
904
|
+
break
|
|
905
|
+
from utils.access_control import access_control
|
|
906
|
+
if not await access_control.check_access(
|
|
907
|
+
user_shortname=logged_in_user,
|
|
908
|
+
space_name=query.space_name,
|
|
909
|
+
subpath=action_obj.get(
|
|
910
|
+
"resource", {}).get("subpath", "/"),
|
|
911
|
+
resource_type=action_obj["resource"]["type"],
|
|
912
|
+
action_type=core.ActionType(action_obj["request"]),
|
|
913
|
+
):
|
|
914
|
+
continue
|
|
915
|
+
records.append(
|
|
916
|
+
core.Record(
|
|
917
|
+
resource_type=action_obj["resource"]["type"],
|
|
918
|
+
shortname=action_obj["resource"]["shortname"],
|
|
919
|
+
subpath=action_obj["resource"]["subpath"],
|
|
920
|
+
attributes=action_obj,
|
|
921
|
+
),
|
|
922
|
+
)
|
|
923
|
+
|
|
924
|
+
return total, records
|
|
925
|
+
|
|
926
|
+
|
|
927
|
+
async def serve_query_aggregation(db, query, user_shortname):
|
|
928
|
+
records = []
|
|
929
|
+
total = 0
|
|
930
|
+
|
|
931
|
+
redis_query_policies = await get_user_query_policies(
|
|
932
|
+
db, user_shortname, query.space_name, query.subpath
|
|
933
|
+
)
|
|
934
|
+
rows = await redis_query_aggregate(
|
|
935
|
+
query=query, redis_query_policies=redis_query_policies
|
|
936
|
+
)
|
|
937
|
+
total = len(rows)
|
|
938
|
+
for idx, row in enumerate(rows):
|
|
939
|
+
record = core.Record(
|
|
940
|
+
resource_type=core.ResourceType.content,
|
|
941
|
+
shortname=str(idx + 1),
|
|
942
|
+
subpath=query.subpath,
|
|
943
|
+
attributes=row["extra_attributes"]
|
|
944
|
+
)
|
|
945
|
+
records.append(record)
|
|
946
|
+
|
|
947
|
+
return total, records
|
|
948
|
+
|
|
949
|
+
def parse_redis_response(rows: list) -> list:
|
|
950
|
+
mylist: list = []
|
|
951
|
+
for one in rows:
|
|
952
|
+
mydict = {}
|
|
953
|
+
key: str | None = None
|
|
954
|
+
for i, value in enumerate(one):
|
|
955
|
+
if i % 2 == 0:
|
|
956
|
+
key = value
|
|
957
|
+
elif key:
|
|
958
|
+
mydict[key] = value
|
|
959
|
+
mylist.append(mydict)
|
|
960
|
+
return mylist
|
|
961
|
+
|
|
962
|
+
async def generate_payload_string(
|
|
963
|
+
db,
|
|
964
|
+
space_name: str,
|
|
965
|
+
subpath: str,
|
|
966
|
+
shortname: str,
|
|
967
|
+
payload: dict,
|
|
968
|
+
):
|
|
969
|
+
payload_string = ""
|
|
970
|
+
# Remove system related attributes from payload
|
|
971
|
+
for attr in RedisServices.SYS_ATTRIBUTES:
|
|
972
|
+
if attr in payload:
|
|
973
|
+
del payload[attr]
|
|
974
|
+
|
|
975
|
+
# Generate direct payload string
|
|
976
|
+
payload_values = set(flatten_all(payload).values())
|
|
977
|
+
payload_string += ",".join([str(i)
|
|
978
|
+
for i in payload_values if i is not None])
|
|
979
|
+
|
|
980
|
+
# Generate attachments payload string
|
|
981
|
+
attachments: dict[str, list] = await db.get_entry_attachments(
|
|
982
|
+
subpath=f"{subpath}/{shortname}",
|
|
983
|
+
attachments_path=(
|
|
984
|
+
settings.spaces_folder
|
|
985
|
+
/ f"{space_name}/{subpath}/.dm/{shortname}"
|
|
986
|
+
),
|
|
987
|
+
retrieve_json_payload=True,
|
|
988
|
+
include_fields=[
|
|
989
|
+
"shortname",
|
|
990
|
+
"displayname",
|
|
991
|
+
"description",
|
|
992
|
+
"payload",
|
|
993
|
+
"tags",
|
|
994
|
+
"owner_shortname",
|
|
995
|
+
"owner_group_shortname",
|
|
996
|
+
"body",
|
|
997
|
+
"state",
|
|
998
|
+
],
|
|
999
|
+
)
|
|
1000
|
+
if not attachments:
|
|
1001
|
+
return payload_string.strip(",")
|
|
1002
|
+
|
|
1003
|
+
# Convert Record objects to dict
|
|
1004
|
+
dict_attachments = {}
|
|
1005
|
+
for k, v in attachments.items():
|
|
1006
|
+
dict_attachments[k] = [i.model_dump() for i in v]
|
|
1007
|
+
|
|
1008
|
+
attachments_values = set(flatten_all(dict_attachments).values())
|
|
1009
|
+
attachments_payload_string = ",".join(
|
|
1010
|
+
[str(i) for i in attachments_values if i is not None]
|
|
1011
|
+
)
|
|
1012
|
+
payload_string += attachments_payload_string
|
|
1013
|
+
return payload_string.strip(",")
|