dmart 0.1.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. alembic/__init__.py +0 -0
  2. alembic/env.py +91 -0
  3. alembic/scripts/__init__.py +0 -0
  4. alembic/scripts/calculate_checksums.py +77 -0
  5. alembic/scripts/migration_f7a4949eed19.py +28 -0
  6. alembic/versions/0f3d2b1a7c21_add_authz_materialized_views.py +87 -0
  7. alembic/versions/10d2041b94d4_last_checksum_history.py +62 -0
  8. alembic/versions/1cf4e1ee3cb8_ext_permission_with_filter_fields_values.py +33 -0
  9. alembic/versions/26bfe19b49d4_rm_failedloginattempts.py +42 -0
  10. alembic/versions/3c8bca2219cc_add_otp_table.py +38 -0
  11. alembic/versions/6675fd9dfe42_remove_unique_from_sessions_table.py +36 -0
  12. alembic/versions/71bc1df82e6a_adding_user_last_login_at.py +43 -0
  13. alembic/versions/74288ccbd3b5_initial.py +264 -0
  14. alembic/versions/7520a89a8467_rm_activesession_table.py +39 -0
  15. alembic/versions/848b623755a4_make_created_nd_updated_at_required.py +138 -0
  16. alembic/versions/8640dcbebf85_add_notes_to_users.py +32 -0
  17. alembic/versions/91c94250232a_adding_fk_on_owner_shortname.py +104 -0
  18. alembic/versions/98ecd6f56f9a_ext_meta_with_owner_group_shortname.py +66 -0
  19. alembic/versions/9aae9138c4ef_indexing_created_at_updated_at.py +80 -0
  20. alembic/versions/__init__.py +0 -0
  21. alembic/versions/b53f916b3f6d_json_to_jsonb.py +492 -0
  22. alembic/versions/eb5f1ec65156_adding_user_locked_to_device.py +36 -0
  23. alembic/versions/f7a4949eed19_adding_query_policies_to_meta.py +60 -0
  24. api/__init__.py +0 -0
  25. api/info/__init__.py +0 -0
  26. api/info/router.py +109 -0
  27. api/managed/__init__.py +0 -0
  28. api/managed/router.py +1541 -0
  29. api/managed/utils.py +1850 -0
  30. api/public/__init__.py +0 -0
  31. api/public/router.py +758 -0
  32. api/qr/__init__.py +0 -0
  33. api/qr/router.py +108 -0
  34. api/user/__init__.py +0 -0
  35. api/user/model/__init__.py +0 -0
  36. api/user/model/errors.py +14 -0
  37. api/user/model/requests.py +165 -0
  38. api/user/model/responses.py +11 -0
  39. api/user/router.py +1401 -0
  40. api/user/service.py +270 -0
  41. bundler.py +44 -0
  42. config/__init__.py +0 -0
  43. config/channels.json +11 -0
  44. config/notification.json +17 -0
  45. data_adapters/__init__.py +0 -0
  46. data_adapters/adapter.py +16 -0
  47. data_adapters/base_data_adapter.py +467 -0
  48. data_adapters/file/__init__.py +0 -0
  49. data_adapters/file/adapter.py +2043 -0
  50. data_adapters/file/adapter_helpers.py +1013 -0
  51. data_adapters/file/archive.py +150 -0
  52. data_adapters/file/create_index.py +331 -0
  53. data_adapters/file/create_users_folders.py +52 -0
  54. data_adapters/file/custom_validations.py +68 -0
  55. data_adapters/file/drop_index.py +40 -0
  56. data_adapters/file/health_check.py +560 -0
  57. data_adapters/file/redis_services.py +1110 -0
  58. data_adapters/helpers.py +27 -0
  59. data_adapters/sql/__init__.py +0 -0
  60. data_adapters/sql/adapter.py +3210 -0
  61. data_adapters/sql/adapter_helpers.py +491 -0
  62. data_adapters/sql/create_tables.py +451 -0
  63. data_adapters/sql/create_users_folders.py +53 -0
  64. data_adapters/sql/db_to_json_migration.py +482 -0
  65. data_adapters/sql/health_check_sql.py +232 -0
  66. data_adapters/sql/json_to_db_migration.py +454 -0
  67. data_adapters/sql/update_query_policies.py +101 -0
  68. data_generator.py +81 -0
  69. dmart-0.1.9.dist-info/METADATA +64 -0
  70. dmart-0.1.9.dist-info/RECORD +149 -0
  71. dmart-0.1.9.dist-info/WHEEL +5 -0
  72. dmart-0.1.9.dist-info/entry_points.txt +2 -0
  73. dmart-0.1.9.dist-info/top_level.txt +23 -0
  74. dmart.py +513 -0
  75. get_settings.py +7 -0
  76. languages/__init__.py +0 -0
  77. languages/arabic.json +15 -0
  78. languages/english.json +16 -0
  79. languages/kurdish.json +14 -0
  80. languages/loader.py +13 -0
  81. main.py +506 -0
  82. migrate.py +24 -0
  83. models/__init__.py +0 -0
  84. models/api.py +203 -0
  85. models/core.py +597 -0
  86. models/enums.py +255 -0
  87. password_gen.py +8 -0
  88. plugins/__init__.py +0 -0
  89. plugins/action_log/__init__.py +0 -0
  90. plugins/action_log/plugin.py +121 -0
  91. plugins/admin_notification_sender/__init__.py +0 -0
  92. plugins/admin_notification_sender/plugin.py +124 -0
  93. plugins/ldap_manager/__init__.py +0 -0
  94. plugins/ldap_manager/plugin.py +100 -0
  95. plugins/local_notification/__init__.py +0 -0
  96. plugins/local_notification/plugin.py +123 -0
  97. plugins/realtime_updates_notifier/__init__.py +0 -0
  98. plugins/realtime_updates_notifier/plugin.py +58 -0
  99. plugins/redis_db_update/__init__.py +0 -0
  100. plugins/redis_db_update/plugin.py +188 -0
  101. plugins/resource_folders_creation/__init__.py +0 -0
  102. plugins/resource_folders_creation/plugin.py +81 -0
  103. plugins/system_notification_sender/__init__.py +0 -0
  104. plugins/system_notification_sender/plugin.py +188 -0
  105. plugins/update_access_controls/__init__.py +0 -0
  106. plugins/update_access_controls/plugin.py +9 -0
  107. pytests/__init__.py +0 -0
  108. pytests/api_user_models_erros_test.py +16 -0
  109. pytests/api_user_models_requests_test.py +98 -0
  110. pytests/archive_test.py +72 -0
  111. pytests/base_test.py +300 -0
  112. pytests/get_settings_test.py +14 -0
  113. pytests/json_to_db_migration_test.py +237 -0
  114. pytests/service_test.py +26 -0
  115. pytests/test_info.py +55 -0
  116. pytests/test_status.py +15 -0
  117. run_notification_campaign.py +98 -0
  118. scheduled_notification_handler.py +121 -0
  119. schema_migration.py +208 -0
  120. schema_modulate.py +192 -0
  121. set_admin_passwd.py +55 -0
  122. sync.py +202 -0
  123. utils/__init__.py +0 -0
  124. utils/access_control.py +306 -0
  125. utils/async_request.py +8 -0
  126. utils/exporter.py +309 -0
  127. utils/firebase_notifier.py +57 -0
  128. utils/generate_email.py +38 -0
  129. utils/helpers.py +352 -0
  130. utils/hypercorn_config.py +12 -0
  131. utils/internal_error_code.py +60 -0
  132. utils/jwt.py +124 -0
  133. utils/logger.py +167 -0
  134. utils/middleware.py +99 -0
  135. utils/notification.py +75 -0
  136. utils/password_hashing.py +16 -0
  137. utils/plugin_manager.py +215 -0
  138. utils/query_policies_helper.py +112 -0
  139. utils/regex.py +44 -0
  140. utils/repository.py +529 -0
  141. utils/router_helper.py +19 -0
  142. utils/settings.py +165 -0
  143. utils/sms_notifier.py +21 -0
  144. utils/social_sso.py +67 -0
  145. utils/templates/activation.html.j2 +26 -0
  146. utils/templates/reminder.html.j2 +17 -0
  147. utils/ticket_sys_utils.py +203 -0
  148. utils/web_notifier.py +29 -0
  149. websocket.py +231 -0
@@ -0,0 +1,482 @@
1
+ #!/usr/bin/env -S BACKEND_ENV=config.env python3
2
+
3
+ import os
4
+ import json
5
+ from datetime import datetime
6
+ from sqlmodel import Session, create_engine, select, col
7
+ from data_adapters.sql.create_tables import (
8
+ Entries,
9
+ Users,
10
+ Attachments,
11
+ Roles,
12
+ Permissions,
13
+ Histories,
14
+ Spaces,
15
+ )
16
+ from models.core import Payload
17
+ from utils.settings import settings
18
+ from models import core
19
+ import base64
20
+
21
+ postgresql_url = f"{settings.database_driver.replace('+asyncpg','+psycopg')}://{settings.database_username}:{settings.database_password}@{settings.database_host}:{settings.database_port}/{settings.database_name}"
22
+ engine = create_engine(postgresql_url, echo=False)
23
+
24
+ def subpath_checker(subpath: str):
25
+ if subpath.endswith("/"):
26
+ subpath = subpath[:-1]
27
+ if not subpath.startswith("/"):
28
+ subpath = "/" + subpath
29
+ return subpath
30
+
31
+ def ensure_directory_exists(path: str):
32
+ os.makedirs(path, exist_ok=True)
33
+
34
+ def clean_json(data: dict):
35
+ if not isinstance(data, dict):
36
+ return data
37
+
38
+ for key, value in list(data.items()):
39
+ if key in ["tags", "mirrors", "active_plugins", "roles", "groups"]:
40
+ continue
41
+ if not value:
42
+ del data[key]
43
+ elif isinstance(value, datetime):
44
+ data[key] = value.isoformat()
45
+ elif isinstance(value, dict):
46
+ clean_json(value)
47
+
48
+ return data
49
+
50
+ def write_json_file(path, data):
51
+ with open(path, "w") as f:
52
+ if data.get("query_policies", False):
53
+ del data["query_policies"]
54
+ clean = clean_json(data)
55
+ json.dump(clean, f, indent=2, default=str)
56
+
57
+ def write_file(path, data):
58
+ with open(path, "w") as f:
59
+ f.write(data)
60
+
61
+ def write_binary_file(path, data):
62
+ with open(path, "wb") as f:
63
+ f.write(data)
64
+
65
+ def process_attachments(session, space_folder):
66
+ attachments = session.exec(select(Attachments)).all()
67
+ for attachment in attachments:
68
+ subpath = subpath_checker(attachment.subpath)
69
+
70
+ parts = subpath.split('/')
71
+ parts.insert(-1, '.dm')
72
+ new_path = '/'.join(parts)
73
+
74
+ dir_path = f"{space_folder}/{attachment.space_name}{new_path}"
75
+ ensure_directory_exists(dir_path)
76
+
77
+ media_path = f"{dir_path}/attachments.{attachment.resource_type}"
78
+ ensure_directory_exists(media_path)
79
+ if attachment.payload.get("body", None) is not None:
80
+ if attachment.payload["content_type"] == 'json':
81
+ write_json_file(f"{media_path}/{attachment.shortname}.json", attachment.payload.get("body", {}))
82
+ attachment.payload["body"] = f"{attachment.shortname}.json"
83
+ else:
84
+ if attachment.media is None:
85
+ print(f"Warning: empty media for @{attachment.space_name}:{attachment.subpath}/{attachment.shortname}")
86
+ continue
87
+ write_binary_file(f"{media_path}/{attachment.payload['body']}", attachment.media)
88
+ _attachment = attachment.model_dump()
89
+
90
+ del _attachment["media"]
91
+ del _attachment["resource_type"]
92
+ write_json_file(f"{media_path}/meta.{attachment.shortname}.json", _attachment)
93
+
94
+ def process_entries(session, space_folder):
95
+ entries = session.exec(select(Entries)).all()
96
+ for entry in entries:
97
+ subpath = subpath_checker(entry.subpath)
98
+ dir_path = f"{space_folder}/{entry.space_name}{subpath}".replace("//", "/") # Ensure absolute path
99
+ ensure_directory_exists(dir_path)
100
+
101
+ if entry.resource_type == "folder":
102
+ dir_meta_path = f"{dir_path}/{entry.shortname}/.dm/".replace("//", "/")
103
+ ensure_directory_exists(dir_meta_path)
104
+ _entry = entry.model_dump()
105
+ body = None
106
+ if _entry.get("payload", None) is not None:
107
+ if _entry.get("payload", None).get("body", None) is not None:
108
+ body = _entry.get("payload", None)["body"]
109
+ _entry["payload"]["body"] = f"{entry.shortname}.json"
110
+ del _entry["space_name"]
111
+ del _entry["subpath"]
112
+ del _entry["resource_type"]
113
+
114
+ write_json_file(f"{dir_meta_path}/meta.folder.json", _entry)
115
+ if body is not None:
116
+ write_json_file(f"{dir_path}/{entry.shortname}.json", body)
117
+ continue
118
+
119
+ dir_meta_path = f"{dir_path}/.dm/{entry.shortname}".replace("//", "/")
120
+ ensure_directory_exists(dir_meta_path)
121
+
122
+ _entry = entry.model_dump()
123
+ del _entry["space_name"]
124
+ del _entry["subpath"]
125
+ del _entry["resource_type"]
126
+
127
+ if entry.payload:
128
+ if "content_type" not in entry.payload:
129
+ print(f"Warning : empty content type for @{entry.space_name}:{entry.subpath}/{entry.shortname}")
130
+ elif entry.payload["content_type"] == core.ContentType.json:
131
+
132
+ if _entry["payload"].get("body", None) is not None:
133
+ if isinstance(_entry["payload"].get("body", None), dict):
134
+ write_json_file(f"{dir_path}/{entry.shortname}.json", _entry["payload"].get("body", None))
135
+
136
+ _entry["payload"]["body"] = f"{entry.shortname}.json"
137
+
138
+ elif entry.payload["content_type"] == core.ContentType.html:
139
+ if _entry["payload"].get("body", None) is not None:
140
+ with open(f"{dir_path}/{entry.shortname}.html", "w", encoding="utf-8") as f:
141
+ f.write(_entry["payload"]["body"])
142
+ _entry["payload"]["body"] = f"{entry.shortname}.html"
143
+
144
+ elif entry.payload["content_type"] == core.ContentType.image:
145
+ if _entry["payload"].get("body", None) is not None:
146
+ body_data = _entry["payload"]["body"]
147
+
148
+ if isinstance(body_data, str):
149
+ if "." in body_data and any(body_data.lower().endswith(ext) for ext in ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.bmp']):
150
+ _entry["payload"]["body"] = body_data
151
+ print(f"Image reference: @{entry.space_name}:{entry.subpath}/{entry.shortname} -> {body_data}")
152
+ else:
153
+ try:
154
+ if len(body_data) % 4 == 0:
155
+ decoded_data = base64.b64decode(body_data, validate=True)
156
+
157
+ extension = "png"
158
+ if "content_sub_type" in entry.payload:
159
+ extension = entry.payload["content_sub_type"].lower()
160
+
161
+ filename = f"{entry.shortname}.{extension}"
162
+ with open(f"{dir_path}/{filename}", "wb") as f:
163
+ f.write(decoded_data)
164
+ _entry["payload"]["body"] = filename
165
+ else:
166
+ print(f"Warning: Invalid image data for @{entry.space_name}:{entry.subpath}/{entry.shortname}")
167
+ _entry["payload"]["body"] = body_data
168
+ except Exception as e:
169
+ print(f"Error processing image @{entry.space_name}:{entry.subpath}/{entry.shortname}: {e}")
170
+ _entry["payload"]["body"] = body_data
171
+ else:
172
+ extension = "png"
173
+ if "content_sub_type" in entry.payload:
174
+ extension = entry.payload["content_sub_type"].lower()
175
+
176
+ filename = f"{entry.shortname}.{extension}"
177
+ with open(f"{dir_path}/{filename}", "wb") as f:
178
+ f.write(body_data)
179
+ _entry["payload"]["body"] = filename
180
+
181
+
182
+ else:
183
+ print(f"Unprocessed content type({entry.payload['content_type']}): @{entry.space_name}:{entry.subpath}/{entry.shortname}")
184
+
185
+ if entry.resource_type != "ticket":
186
+ del _entry["state"]
187
+ del _entry["is_open"]
188
+ del _entry["reporter"]
189
+ del _entry["workflow_shortname"]
190
+ del _entry["collaborators"]
191
+ del _entry["resolution_reason"]
192
+
193
+ write_json_file(f"{dir_meta_path}/meta.{entry.resource_type}.json", _entry)
194
+
195
+ def process_users(session, space_folder):
196
+ users = session.exec(select(Users)).all()
197
+ dir_path = f"{space_folder}/management/users" # Ensure absolute path
198
+ for user in users:
199
+ dir_meta_path = f"{dir_path}/.dm/{user.shortname}"
200
+ ensure_directory_exists(dir_meta_path)
201
+
202
+ _user = user.model_dump()
203
+ del _user["space_name"]
204
+ del _user["resource_type"]
205
+ if _user.get("payload", None) and _user["payload"].get("body" , None):
206
+ write_json_file(
207
+ f"{dir_path}/{user.shortname}.json",
208
+ _user["payload"]["body"]
209
+ )
210
+ _user["payload"]["body"] = f"{user.shortname}.json"
211
+
212
+ write_json_file(f"{dir_meta_path}/meta.user.json", _user)
213
+
214
+ def process_roles(session, space_folder):
215
+ roles = session.exec(select(Roles)).all()
216
+ dir_path = f"{space_folder}/management/roles/.dm" # Ensure absolute path
217
+ for role in roles:
218
+ ensure_directory_exists(f"{dir_path}/{role.shortname}")
219
+
220
+ _role = role.model_dump()
221
+ del _role["space_name"]
222
+ del _role["subpath"]
223
+ del _role["resource_type"]
224
+
225
+ write_json_file(f"{dir_path}/{role.shortname}/meta.role.json", _role)
226
+
227
+ def process_permissions(session, space_folder):
228
+ permissions = session.exec(select(Permissions)).all()
229
+ dir_path = f"{space_folder}/management/permissions/.dm"
230
+ for permission in permissions:
231
+ ensure_directory_exists(f"{dir_path}/{permission.shortname}")
232
+
233
+ _permission = permission.model_dump()
234
+ del _permission["space_name"]
235
+ del _permission["subpath"]
236
+ del _permission["resource_type"]
237
+
238
+ write_json_file(f"{dir_path}/{permission.shortname}/meta.permission.json", _permission)
239
+
240
+ def process_histories(session, space_folder):
241
+ histories = session.exec(select(Histories)).all()
242
+ for history in histories:
243
+ dir_path = f"{space_folder}/{history.space_name}" # Ensure absolute path
244
+ ensure_directory_exists(dir_path)
245
+
246
+ file_path = f"{dir_path}{history.subpath}/.dm/{history.shortname}"
247
+ ensure_directory_exists(file_path)
248
+
249
+ _history_one: str = history.model_dump_json()
250
+ _history: dict = json.loads(_history_one)
251
+ _history["shortname"] = "history"
252
+
253
+ del _history["space_name"]
254
+ del _history["subpath"]
255
+ if _history.get("resource_type", None):
256
+ del _history["resource_type"]
257
+ with open(f"{file_path}/history.jsonl", "a+") as f:
258
+ f.write(json.dumps(_history) + "\n")
259
+
260
+ def process_spaces(session, space_folder):
261
+ spaces = session.exec(select(Spaces)).all()
262
+ for space in spaces:
263
+ dir_path = f"{space_folder}/{space.space_name}/.dm/"
264
+ ensure_directory_exists(dir_path)
265
+
266
+ _space = space.model_dump()
267
+ del _space["space_name"]
268
+ del _space["resource_type"]
269
+
270
+ write_json_file(f"{dir_path}/meta.space.json", _space)
271
+
272
+ async def export_data_with_query(query, user_shortname):
273
+ from utils.repository import serve_query
274
+
275
+ space_folder = os.path.relpath(str(settings.spaces_folder))
276
+
277
+ total, records = await serve_query(query, user_shortname)
278
+
279
+ with (Session(engine) as session):
280
+ space = session.exec(select(Spaces).where(col(Spaces.space_name) == query.space_name)).first()
281
+ if space:
282
+ dir_path = f"{space_folder}/{space.space_name}/.dm/"
283
+ ensure_directory_exists(dir_path)
284
+
285
+ _space = space.model_dump()
286
+ del _space["space_name"]
287
+ del _space["resource_type"]
288
+
289
+ write_json_file(f"{dir_path}/meta.space.json", _space)
290
+ if query.subpath and query.subpath != "/":
291
+ path_parts = query.subpath.strip("/").split("/")
292
+ current_path = ""
293
+
294
+ for part in path_parts:
295
+ current_path += f"/{part}"
296
+
297
+ folder = session.exec(select(Entries).where(
298
+ (Entries.space_name == query.space_name) &
299
+ (Entries.subpath == str(current_path.rsplit("/", 1)[0] or "/")) &
300
+ (Entries.shortname == part) &
301
+ (Entries.resource_type == "folder")
302
+ )).first()
303
+
304
+ if folder:
305
+ folder_subpath = subpath_checker(folder.subpath)
306
+ folder_dir_path = f"{space_folder}/{folder.space_name}{folder_subpath}".replace("//", "/")
307
+ ensure_directory_exists(folder_dir_path)
308
+
309
+ dir_meta_path = f"{folder_dir_path}/{folder.shortname}/.dm/".replace("//", "/")
310
+ ensure_directory_exists(dir_meta_path)
311
+
312
+ _folder = folder.model_dump()
313
+ _folder = {
314
+ **_folder,
315
+ **_folder.get("attributes", {})
316
+ }
317
+ if "attributes" in _folder:
318
+ del _folder["attributes"]
319
+ body = None
320
+ if _folder and _folder.get("payload", None) is not None:
321
+ if _folder and _folder.get("payload", {}).get("body", None) is not None:
322
+ body = _folder.get("payload", {}).get("body", None)
323
+ _folder["payload"]["body"] = f"{folder.shortname}.json"
324
+
325
+ del _folder["space_name"]
326
+ del _folder["subpath"]
327
+ del _folder["resource_type"]
328
+
329
+ write_json_file(f"{dir_meta_path}/meta.folder.json", _folder)
330
+ if body is not None:
331
+ write_json_file(f"{folder_dir_path}/{folder.shortname}.json", body)
332
+
333
+ for entry in records:
334
+ subpath = subpath_checker(entry.subpath)
335
+ dir_path = f"{space_folder}/{query.space_name}{subpath}".replace("//", "/") # Ensure absolute path
336
+ ensure_directory_exists(dir_path)
337
+
338
+ if entry.resource_type == "folder":
339
+ dir_meta_path = f"{dir_path}/{entry.shortname}/.dm/".replace("//", "/")
340
+ ensure_directory_exists(dir_meta_path)
341
+ _entry = entry.model_dump()
342
+ body = None
343
+ if _entry.get("payload", None) is not None:
344
+ if _entry.get("payload", {}).get("body", None) is not None:
345
+ body = _entry.get("payload",{}).get("body", None)
346
+ _entry["payload"]["body"] = f"{entry.shortname}.json"
347
+
348
+ del _entry["subpath"]
349
+ del _entry["resource_type"]
350
+
351
+ _entry = {
352
+ **_entry,
353
+ **_entry.get("attributes", {})
354
+ }
355
+ if "attributes" in _entry:
356
+ del _entry["attributes"]
357
+
358
+ write_json_file(f"{dir_meta_path}/meta.folder.json", _entry)
359
+ if body is not None:
360
+ write_json_file(f"{dir_path}/{entry.shortname}.json", body)
361
+ continue
362
+
363
+ dir_meta_path = f"{dir_path}/.dm/{entry.shortname}".replace("//", "/")
364
+ ensure_directory_exists(dir_meta_path)
365
+
366
+ _entry = entry.model_dump()
367
+ del _entry["subpath"]
368
+ del _entry["resource_type"]
369
+
370
+ if entry.attributes.get("payload"):
371
+ if entry.attributes.get("payload", {}).get("content_type") == core.ContentType.json:
372
+ if _entry.get("attributes",{}).get("payload",{}).get("body", None) is not None:
373
+ if isinstance( _entry.get("attributes",{}).get("payload").get("body", None), dict):
374
+ write_json_file(f"{dir_path}/{entry.shortname}.json", _entry.get("attributes",{}).get("payload").get("body", None))
375
+ _entry.get("attributes",{}).get("payload")["body"] = f"{entry.shortname}.json"
376
+
377
+ _entry = {
378
+ **_entry,
379
+ **_entry.get("attributes",{})
380
+ }
381
+ if "attributes" in _entry:
382
+ del _entry["attributes"]
383
+ if "attachments" in _entry:
384
+ del _entry["attachments"]
385
+
386
+ write_json_file(f"{dir_meta_path}/meta.{entry.resource_type}.json", _entry)
387
+
388
+ histories = session.exec(select(Histories).where(
389
+ (Histories.space_name == query.space_name) &
390
+ (Histories.subpath == entry.subpath) &
391
+ (Histories.shortname == entry.shortname)
392
+ )).all()
393
+
394
+ for history in histories:
395
+ file_path = f"{dir_path}/.dm/{entry.shortname}"
396
+ ensure_directory_exists(file_path)
397
+
398
+ _history_one: str = history.model_dump_json()
399
+ _history: dict = json.loads(_history_one)
400
+ _history["shortname"] = "history"
401
+
402
+ del _history["space_name"]
403
+ del _history["subpath"]
404
+ if _history.get("resource_type", None):
405
+ del _history["resource_type"]
406
+ with open(f"{file_path}/history.jsonl", "a+") as f:
407
+ f.write(json.dumps(_history) + "\n")
408
+
409
+ attachments = session.exec(select(Attachments).where(
410
+ (Attachments.space_name == query.space_name) &
411
+ (Attachments.subpath == f"/{entry.subpath}/{entry.shortname}")
412
+ )).all()
413
+
414
+ __attachment = None
415
+ for attachment in attachments:
416
+ __attachment = attachment
417
+ subpath = subpath_checker(attachment.subpath)
418
+
419
+ parts = subpath.split('/')
420
+ parts.insert(-1, '.dm')
421
+ new_path = '/'.join(parts)
422
+
423
+ dir_path = f"{space_folder}/{query.space_name}{new_path}"
424
+ ensure_directory_exists(dir_path)
425
+
426
+ media_path = f"{dir_path}/attachments.{attachment.resource_type}"
427
+ ensure_directory_exists(media_path)
428
+
429
+ attachment_body = None
430
+ if attachment.payload is not None:
431
+ if isinstance(attachment.payload, Payload):
432
+ attachment_body = attachment.payload.body
433
+ else:
434
+ attachment_body = attachment.payload["body"]
435
+
436
+ if attachment_body is not None:
437
+ if isinstance(attachment.payload, dict) and attachment.payload.get("content_type") == 'json':
438
+ write_json_file(f"{media_path}/{attachment.shortname}.json", attachment_body)
439
+ attachment.payload["body"] = f"{attachment.shortname}.json"
440
+ elif isinstance(attachment.payload, dict) and attachment.payload.get("content_type") == 'comment':
441
+ write_json_file(f"{media_path}/{attachment.shortname}.json", attachment_body)
442
+ attachment.payload["body"] = f"{attachment.shortname}.json"
443
+ else:
444
+ if attachment.media:
445
+ write_binary_file(f"{media_path}/{attachment_body}", attachment.media)
446
+
447
+ _attachment = attachment.model_dump()
448
+
449
+ del _attachment["media"]
450
+ del _attachment["resource_type"]
451
+ write_json_file(f"{media_path}/meta.{attachment.shortname}.json", _attachment)
452
+ else:
453
+ with open(f"{dir_meta_path}/meta.{entry.resource_type}.json", "r") as f:
454
+ entry_data = json.load(f)
455
+ if __attachment is not None:
456
+ if "payload" in entry_data:
457
+ entry_data["payload"] = __attachment.payload
458
+
459
+ return space_folder
460
+
461
+ def main():
462
+ space_folder = os.path.relpath(str(settings.spaces_folder))
463
+
464
+ with Session(engine) as session:
465
+ print("Processing spaces...")
466
+ process_spaces(session, space_folder)
467
+ print("Processing entries...")
468
+ process_entries(session, space_folder)
469
+ print("Processing users...")
470
+ process_users(session, space_folder)
471
+ print("Processing roles...")
472
+ process_roles(session, space_folder)
473
+ print("Processing permissions...")
474
+ process_permissions(session, space_folder)
475
+ print("Processing attachments...")
476
+ process_attachments(session, space_folder)
477
+ print("Processing histories...")
478
+ process_histories(session, space_folder)
479
+
480
+
481
+ if __name__ == "__main__":
482
+ main()