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
dmart.py ADDED
@@ -0,0 +1,513 @@
1
+ #!/usr/bin/env -S BACKEND_ENV=config.env python3
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import asyncio
6
+ import json
7
+ import os
8
+ import shutil
9
+ import ssl
10
+ import subprocess
11
+ import sys
12
+ import time
13
+ import warnings
14
+ from multiprocessing import freeze_support
15
+ from pathlib import Path
16
+
17
+ from hypercorn.config import Config
18
+ from hypercorn.run import run
19
+
20
+ from data_adapters.file.archive import archive
21
+ from data_adapters.file.create_index import main as create_index
22
+ from data_adapters.file.health_check import main as health_check
23
+ from data_adapters.sql.json_to_db_migration import main as json_to_db_migration
24
+ from data_adapters.sql.db_to_json_migration import main as db_to_json_migration
25
+ from main import main as server
26
+ from utils.exporter import main as exporter, exit_with_error, OUTPUT_FOLDER_NAME, validate_config, extract
27
+ from utils.settings import settings
28
+
29
+ freeze_support()
30
+
31
+ commands = """ server
32
+ hyper
33
+ health-check
34
+ create-index
35
+ export
36
+ settings
37
+ set_password
38
+ archive
39
+ json_to_db
40
+ db_to_json
41
+ help
42
+ version
43
+ info
44
+ """
45
+
46
+ sentinel = object()
47
+
48
+
49
+ def hypercorn_main() -> int:
50
+ parser = argparse.ArgumentParser()
51
+ parser.add_argument(
52
+ "application", help="The application to dispatch to as path.to.module:instance.path"
53
+ )
54
+ parser.add_argument("--access-log", help="Deprecated, see access-logfile", default=sentinel)
55
+ parser.add_argument(
56
+ "--access-logfile",
57
+ help="The target location for the access log, use `-` for stdout",
58
+ default=sentinel,
59
+ )
60
+ parser.add_argument(
61
+ "--access-logformat",
62
+ help="The log format for the access log, see help docs",
63
+ default=sentinel,
64
+ )
65
+ parser.add_argument(
66
+ "--backlog", help="The maximum number of pending connections", type=int, default=sentinel
67
+ )
68
+ parser.add_argument(
69
+ "-b",
70
+ "--bind",
71
+ dest="binds",
72
+ help=""" The TCP host/address to bind to. Should be either host:port, host,
73
+ unix:path or fd://num, e.g. 127.0.0.1:5000, 127.0.0.1,
74
+ unix:/tmp/socket or fd://33 respectively. """,
75
+ default=[],
76
+ action="append",
77
+ )
78
+ parser.add_argument("--ca-certs", help="Path to the SSL CA certificate file", default=sentinel)
79
+ parser.add_argument("--certfile", help="Path to the SSL certificate file", default=sentinel)
80
+ parser.add_argument("--cert-reqs", help="See verify mode argument", type=int, default=sentinel)
81
+ parser.add_argument("--ciphers", help="Ciphers to use for the SSL setup", default=sentinel)
82
+ parser.add_argument(
83
+ "-c",
84
+ "--config",
85
+ help="Location of a TOML config file, or when prefixed with `file:` a Python file, or when prefixed with `python:` a Python module.", # noqa: E501
86
+ default=None,
87
+ )
88
+ parser.add_argument(
89
+ "--debug",
90
+ help="Enable debug mode, i.e. extra logging and checks",
91
+ action="store_true",
92
+ default=sentinel,
93
+ )
94
+ parser.add_argument("--error-log", help="Deprecated, see error-logfile", default=sentinel)
95
+ parser.add_argument(
96
+ "--error-logfile",
97
+ "--log-file",
98
+ dest="error_logfile",
99
+ help="The target location for the error log, use `-` for stderr",
100
+ default=sentinel,
101
+ )
102
+ parser.add_argument(
103
+ "--graceful-timeout",
104
+ help="""Time to wait after SIGTERM or Ctrl-C for any remaining requests (tasks)
105
+ to complete.""",
106
+ default=sentinel,
107
+ type=int,
108
+ )
109
+ parser.add_argument(
110
+ "--read-timeout",
111
+ help="""Seconds to wait before timing out reads on TCP sockets""",
112
+ default=sentinel,
113
+ type=int,
114
+ )
115
+ parser.add_argument(
116
+ "--max-requests",
117
+ help="""Maximum number of requests a worker will process before restarting""",
118
+ default=sentinel,
119
+ type=int,
120
+ )
121
+ parser.add_argument(
122
+ "--max-requests-jitter",
123
+ help="This jitter causes the max-requests per worker to be "
124
+ "randomized by randint(0, max_requests_jitter)",
125
+ default=sentinel,
126
+ type=int,
127
+ )
128
+ parser.add_argument(
129
+ "-g", "--group", help="Group to own any unix sockets.", default=sentinel, type=int
130
+ )
131
+ parser.add_argument(
132
+ "-k",
133
+ "--worker-class",
134
+ dest="worker_class",
135
+ help="The type of worker to use. "
136
+ "Options include asyncio, uvloop (pip install hypercorn[uvloop]), "
137
+ "and trio (pip install hypercorn[trio]).",
138
+ default=sentinel,
139
+ )
140
+ parser.add_argument(
141
+ "--keep-alive",
142
+ help="Seconds to keep inactive connections alive for",
143
+ default=sentinel,
144
+ type=int,
145
+ )
146
+ parser.add_argument("--keyfile", help="Path to the SSL key file", default=sentinel)
147
+ parser.add_argument(
148
+ "--keyfile-password", help="Password to decrypt the SSL key file", default=sentinel
149
+ )
150
+ parser.add_argument(
151
+ "--insecure-bind",
152
+ dest="insecure_binds",
153
+ help="""The TCP host/address to bind to. SSL options will not apply to these binds.
154
+ See *bind* for formatting options. Care must be taken! See HTTP -> HTTPS redirection docs.
155
+ """,
156
+ default=[],
157
+ action="append",
158
+ )
159
+ parser.add_argument(
160
+ "--log-config",
161
+ help=""""A Python logging configuration file. This can be prefixed with
162
+ 'json:' or 'toml:' to load the configuration from a file in
163
+ that format. Default is the logging ini format.""",
164
+ default=sentinel,
165
+ )
166
+ parser.add_argument(
167
+ "--log-level", help="The (error) log level, defaults to info", default=sentinel
168
+ )
169
+ parser.add_argument(
170
+ "-p", "--pid", help="Location to write the PID (Program ID) to.", default=sentinel
171
+ )
172
+ parser.add_argument(
173
+ "--quic-bind",
174
+ dest="quic_binds",
175
+ help="""The UDP/QUIC host/address to bind to. See *bind* for formatting
176
+ options.
177
+ """,
178
+ default=[],
179
+ action="append",
180
+ )
181
+ parser.add_argument(
182
+ "--reload",
183
+ help="Enable automatic reloads on code changes",
184
+ action="store_true",
185
+ default=sentinel,
186
+ )
187
+ parser.add_argument(
188
+ "--root-path", help="The setting for the ASGI root_path variable", default=sentinel
189
+ )
190
+ parser.add_argument(
191
+ "--server-name",
192
+ dest="server_names",
193
+ help="""The hostnames that can be served, requests to different hosts
194
+ will be responded to with 404s.
195
+ """,
196
+ default=[],
197
+ action="append",
198
+ )
199
+ parser.add_argument(
200
+ "--statsd-host", help="The host:port of the statsd server", default=sentinel
201
+ )
202
+ parser.add_argument("--statsd-prefix", help="Prefix for all statsd messages", default="")
203
+ parser.add_argument(
204
+ "-m",
205
+ "--umask",
206
+ help="The permissions bit mask to use on any unix sockets.",
207
+ default=sentinel,
208
+ type=int,
209
+ )
210
+ parser.add_argument(
211
+ "-u", "--user", help="User to own any unix sockets.", default=sentinel, type=int
212
+ )
213
+
214
+ def _convert_verify_mode(value: str) -> ssl.VerifyMode:
215
+ try:
216
+ return ssl.VerifyMode[value]
217
+ except KeyError:
218
+ raise argparse.ArgumentTypeError(f"'{value}' is not a valid verify mode")
219
+
220
+ parser.add_argument(
221
+ "--verify-mode",
222
+ help="SSL verify mode for peer's certificate, see ssl.VerifyMode enum for possible values.",
223
+ type=_convert_verify_mode,
224
+ default=sentinel,
225
+ )
226
+ parser.add_argument(
227
+ "--websocket-ping-interval",
228
+ help="""If set this is the time in seconds between pings sent to the client.
229
+ This can be used to keep the websocket connection alive.""",
230
+ default=sentinel,
231
+ type=int,
232
+ )
233
+ parser.add_argument(
234
+ "-w",
235
+ "--workers",
236
+ dest="workers",
237
+ help="The number of workers to spawn and use",
238
+ default=sentinel,
239
+ type=int,
240
+ )
241
+ args = parser.parse_args(sys.argv[1:])
242
+ config = Config.from_toml(args.config)
243
+ config.application_path = args.application
244
+
245
+ if args.log_level is not sentinel:
246
+ config.loglevel = args.log_level
247
+ if args.access_logformat is not sentinel:
248
+ config.access_log_format = args.access_logformat
249
+ if args.access_log is not sentinel:
250
+ warnings.warn(
251
+ "The --access-log argument is deprecated, use `--access-logfile` instead",
252
+ DeprecationWarning,
253
+ )
254
+ config.accesslog = args.access_log
255
+ if args.access_logfile is not sentinel:
256
+ config.accesslog = args.access_logfile
257
+ if args.backlog is not sentinel:
258
+ config.backlog = args.backlog
259
+ if args.ca_certs is not sentinel:
260
+ config.ca_certs = args.ca_certs
261
+ if args.certfile is not sentinel:
262
+ config.certfile = args.certfile
263
+ if args.cert_reqs is not sentinel:
264
+ config.cert_reqs = args.cert_reqs
265
+ if args.ciphers is not sentinel:
266
+ config.ciphers = args.ciphers
267
+ if args.debug is not sentinel:
268
+ config.debug = args.debug
269
+ if args.error_log is not sentinel:
270
+ warnings.warn(
271
+ "The --error-log argument is deprecated, use `--error-logfile` instead",
272
+ DeprecationWarning,
273
+ )
274
+ config.errorlog = args.error_log
275
+ if args.error_logfile is not sentinel:
276
+ config.errorlog = args.error_logfile
277
+ if args.graceful_timeout is not sentinel:
278
+ config.graceful_timeout = args.graceful_timeout
279
+ if args.read_timeout is not sentinel:
280
+ config.read_timeout = args.read_timeout
281
+ if args.group is not sentinel:
282
+ config.group = args.group
283
+ if args.keep_alive is not sentinel:
284
+ config.keep_alive_timeout = args.keep_alive
285
+ if args.keyfile is not sentinel:
286
+ config.keyfile = args.keyfile
287
+ if args.keyfile_password is not sentinel:
288
+ config.keyfile_password = args.keyfile_password
289
+ if args.log_config is not sentinel:
290
+ config.logconfig = args.log_config
291
+ if args.max_requests is not sentinel:
292
+ config.max_requests = args.max_requests
293
+ if args.max_requests_jitter is not sentinel:
294
+ config.max_requests_jitter = args.max_requests
295
+ if args.pid is not sentinel:
296
+ config.pid_path = args.pid
297
+ if args.root_path is not sentinel:
298
+ config.root_path = args.root_path
299
+ if args.reload is not sentinel:
300
+ config.use_reloader = args.reload
301
+ if args.statsd_host is not sentinel:
302
+ config.statsd_host = args.statsd_host
303
+ if args.statsd_prefix is not sentinel:
304
+ config.statsd_prefix = args.statsd_prefix
305
+ if args.umask is not sentinel:
306
+ config.umask = args.umask
307
+ if args.user is not sentinel:
308
+ config.user = args.user
309
+ if args.worker_class is not sentinel:
310
+ config.worker_class = args.worker_class
311
+ if args.verify_mode is not sentinel:
312
+ config.verify_mode = args.verify_mode
313
+ if args.websocket_ping_interval is not sentinel:
314
+ config.websocket_ping_interval = args.websocket_ping_interval
315
+ if args.workers is not sentinel:
316
+ config.workers = args.workers
317
+
318
+ if len(args.binds) > 0:
319
+ config.bind = args.binds
320
+ if len(args.insecure_binds) > 0:
321
+ config.insecure_bind = args.insecure_binds
322
+ if len(args.quic_binds) > 0:
323
+ config.quic_bind = args.quic_binds
324
+ if len(args.server_names) > 0:
325
+ config.server_names = args.server_names
326
+
327
+ return run(config)
328
+
329
+
330
+ def main():
331
+ sys.argv = sys.argv[1:]
332
+ if len(sys.argv) == 0:
333
+ print("You must provide a command to run:")
334
+ print(commands)
335
+ sys.exit(1)
336
+
337
+ match sys.argv[0]:
338
+ case "hyper":
339
+ if len(sys.argv) == 1:
340
+ print("Running Hypercorn with default settings")
341
+ default_params = "main:app --config hypercorn_config.toml"
342
+ print(f">{default_params}")
343
+ sys.argv = ["hyper"] + default_params.split(" ")
344
+ hypercorn_main()
345
+ case "server":
346
+ asyncio.run(server())
347
+ case "health-check":
348
+ parser = argparse.ArgumentParser(
349
+ description="This created for doing health check functionality",
350
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
351
+ )
352
+ parser.add_argument("-t", "--type", help="type of health check (soft or hard)")
353
+ parser.add_argument("-s", "--space", help="hit the target space or pass (all) to make the full health check")
354
+ parser.add_argument("-m", "--schemas", nargs="*", help="hit the target schema inside the space")
355
+
356
+ args = parser.parse_args()
357
+ before_time = time.time()
358
+ asyncio.run(health_check(args.type, args.space, args.schemas))
359
+ print(f'total time: {"{:.2f}".format(time.time() - before_time)} sec')
360
+ case "create-index":
361
+ parser = argparse.ArgumentParser(
362
+ description="Recreate Redis indices based on the available schema definitions",
363
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
364
+ )
365
+ parser.add_argument("-p", "--space", help="recreate indices for this space only")
366
+ parser.add_argument(
367
+ "-c", "--schemas", nargs="*", help="recreate indices for this schemas only"
368
+ )
369
+ parser.add_argument(
370
+ "-s", "--subpaths", nargs="*", help="upload documents for this subpaths only"
371
+ )
372
+ parser.add_argument(
373
+ "--flushall", action='store_true', help="FLUSHALL data on Redis"
374
+ )
375
+
376
+ args = parser.parse_args()
377
+
378
+ asyncio.run(create_index(args.space, args.schemas, args.subpaths, args.flushall))
379
+ case "export":
380
+ parser = argparse.ArgumentParser()
381
+ parser.add_argument(
382
+ "--config", required=True, help="Json config relative path from the script"
383
+ )
384
+ parser.add_argument(
385
+ "--spaces", required=True, help="Spaces relative path from the script"
386
+ )
387
+ parser.add_argument(
388
+ "--output",
389
+ help="Output relative path from the script (the default path is the current script path",
390
+ )
391
+ parser.add_argument(
392
+ "--since",
393
+ help="Export entries created/updated since the provided timestamp",
394
+ )
395
+ args = parser.parse_args()
396
+ since = None
397
+ output_path = ""
398
+ if args.output:
399
+ output_path = args.output
400
+
401
+ if args.since:
402
+ since = int(round(float(args.since) * 1000))
403
+
404
+ if not os.path.isdir(args.spaces):
405
+ exit_with_error(f"The spaces folder {args.spaces} is not found.")
406
+
407
+ out_path = os.path.join(output_path, OUTPUT_FOLDER_NAME)
408
+ if os.path.isdir(out_path):
409
+ shutil.rmtree(out_path)
410
+
411
+ tasks = []
412
+ with open(args.config, "r") as f:
413
+ config_objs = json.load(f)
414
+
415
+ for config_obj in config_objs:
416
+ if not validate_config(config_obj):
417
+ continue
418
+ tasks.append(extract(config_obj.get("space", ""),
419
+ config_obj.get("subpath", ""),
420
+ config_obj.get("resource_type", ""),
421
+ config_obj.get("schema_shortname", ""),
422
+ config_obj.get("included_meta_fields", {}),
423
+ config_obj.get("excluded_payload_fields", {}),
424
+ args.spaces, output_path, since))
425
+
426
+ asyncio.run(exporter(tasks))
427
+
428
+ print(
429
+ f"Output path: {os.path.abspath(os.path.join(output_path, OUTPUT_FOLDER_NAME))}"
430
+ )
431
+ case "settings":
432
+ print(settings.model_dump_json())
433
+ case "set_password":
434
+ import set_admin_passwd # noqa: F401
435
+ case "archive":
436
+ parser = argparse.ArgumentParser(
437
+ description="Script for archiving records from different spaces and subpaths."
438
+ )
439
+ parser.add_argument("space", type=str, help="The name of the space")
440
+ parser.add_argument("subpath", type=str, help="The subpath within the space")
441
+ parser.add_argument(
442
+ "schema",
443
+ type=str,
444
+ help="The subpath within the space. Optional, if not provided move everything",
445
+ nargs="?",
446
+ )
447
+ parser.add_argument(
448
+ "olderthan",
449
+ type=int,
450
+ help="The number of day, older than which, the entries will be archived (based on updated_at)",
451
+ )
452
+
453
+ args = parser.parse_args()
454
+ space = args.space
455
+ subpath = args.subpath
456
+ olderthan = args.olderthan
457
+ schema = args.schema or "meta"
458
+
459
+ asyncio.run(archive(space, subpath, schema, olderthan))
460
+ print("Done.")
461
+ case "json_to_db":
462
+ asyncio.run(json_to_db_migration())
463
+ case "db_to_json":
464
+ db_to_json_migration()
465
+ case "help":
466
+ print("Available commands:")
467
+ print(commands)
468
+ case "version":
469
+ info_json_path = Path(__file__).resolve().parent / "info.json"
470
+ if info_json_path.exists():
471
+ with open(info_json_path) as info:
472
+ print(json.load(info).get("tag"))
473
+ else:
474
+ tag_cmd = "git describe --tags"
475
+ result, _ = subprocess.Popen(tag_cmd.split(" "), stdout=subprocess.PIPE,
476
+ stderr=subprocess.PIPE).communicate()
477
+ tag = None if result is None or len(result) == 0 else result.decode().strip()
478
+ print(tag)
479
+ case "info":
480
+ info_json_path = Path(__file__).resolve().parent / "info.json"
481
+ if info_json_path.exists():
482
+ with open(info_json_path) as info:
483
+ print(json.load(info))
484
+ else:
485
+ branch_cmd = "git rev-parse --abbrev-ref HEAD"
486
+ result, _ = subprocess.Popen(branch_cmd.split(" "), stdout=subprocess.PIPE,
487
+ stderr=subprocess.PIPE).communicate()
488
+ branch = None if result is None or len(result) == 0 else result.decode().strip()
489
+
490
+ version_cmd = "git rev-parse --short HEAD"
491
+ result, _ = subprocess.Popen(version_cmd.split(" "), stdout=subprocess.PIPE,
492
+ stderr=subprocess.PIPE).communicate()
493
+ version = None if result is None or len(result) == 0 else result.decode().strip()
494
+
495
+ tag_cmd = "git describe --tags"
496
+ result, _ = subprocess.Popen(tag_cmd.split(" "), stdout=subprocess.PIPE,
497
+ stderr=subprocess.PIPE).communicate()
498
+ tag = None if result is None or len(result) == 0 else result.decode().strip()
499
+
500
+ version_date_cmd = "git show --pretty=format:'%ad'"
501
+ result, _ = subprocess.Popen(version_date_cmd.split(" "), stdout=subprocess.PIPE,
502
+ stderr=subprocess.PIPE).communicate()
503
+ version_date = None if result is None or len(result) == 0 else result.decode().split("\n")[0]
504
+
505
+ print({
506
+ "commit_hash": version,
507
+ "date": version_date,
508
+ "branch": branch,
509
+ "tag": tag
510
+ })
511
+
512
+ if __name__ == "__main__":
513
+ main()
get_settings.py ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env -S BACKEND_ENV=config.env python3
2
+
3
+ from utils.settings import settings
4
+
5
+
6
+ if __name__ == "__main__":
7
+ print(settings.model_dump_json())
languages/__init__.py ADDED
File without changes
languages/arabic.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "invitation_message": "تهانينا، لقد تم الآن إنشاء حساب الخاص بك، يرجى اتباع هذا الرابط للتأكيد وتسجيل الدخول: {link} يمكن استخدام هذا الرابط مرة واحدة وخلال الـ 48 ساعة القادمة.",
3
+ "reset_message": "تم إرسال رابط إعادة تعيين كلمة المرور إلى بريدك الإلكتروني. {link} يمكن استخدام هذا الرابط مرة واحدة وخلال الـ 48 ساعة القادمة.",
4
+ "rejected": "مرفوض",
5
+ "pending": "قيد المراجعة",
6
+ "cancelled": "مُلغى",
7
+ "assigned": "مُعيّن",
8
+ "failed": "فشل التسليم",
9
+ "confirmed": "مؤكد",
10
+ "new": "جديد",
11
+ "completed": "مكتمل",
12
+ "approved": "مقبول",
13
+ "delivered": "تم التسليم",
14
+ "contract_submitted": "تم تصعيد العقد"
15
+ }
languages/english.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "invitation_message": "Congratulations, your account is now created, please follow this link to confirm and login: {link} This link can be used once and within the next 48 hours.",
3
+ "reset_message": "A password reset link has been sent to your email. {link} This link can be used once and within the next 48 hours.",
4
+ "rejected": "rejected",
5
+ "accepted": "accepted",
6
+ "pending": "pending",
7
+ "cancelled": "cancelled",
8
+ "assigned": "assigned",
9
+ "failed": "failed",
10
+ "confirmed": "confirmed",
11
+ "new": "new",
12
+ "completed": "completed",
13
+ "approved": "approved",
14
+ "contract_submitted": "contract submitted"
15
+
16
+ }
languages/kurdish.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "invitation_message": "لە ڕێگەی ئەم بەستەرەوە ئەکاونتەکەت پشتڕاست بکەرەوە: {link} ئەم بەستەرە دەتوانرێت جارێک و لە ماوەی ٤٨ کاتژمێری داهاتوودا بەکاربهێنرێت.",
3
+ "reset_message": "بەستەری گۆڕانکاری وشەی نهێنی بۆ ئیمەیلەکەت نێردرا. {link} ئەم بەستەرە دەتوانرێت جارێک و لە ماوەی ٤٨ کاتژمێری داهاتوودا بەکاربهێنرێت.",
4
+ "rejected": "مرفوض",
5
+ "pending": "قيد الانتظار",
6
+ "cancelled": "ألغيت",
7
+ "assigned": "مُكَلَّف",
8
+ "failed": "فشل",
9
+ "confirmed": "مؤكد",
10
+ "new": "new",
11
+ "completed": "مكتمل",
12
+ "approved": "موافقة",
13
+ "contract_submitted": "تم تقديم العقد"
14
+ }
languages/loader.py ADDED
@@ -0,0 +1,13 @@
1
+ import glob
2
+ import json
3
+ from pathlib import Path
4
+
5
+
6
+ languages: dict[str, dict[str, str]] = {}
7
+ def load_langs() -> None:
8
+ languages_dir = Path(__file__).resolve().parent
9
+ language_list = list(languages_dir.glob("*.json"))
10
+ for lang in language_list:
11
+ lang_code = lang.stem
12
+ with open(lang, 'r', encoding='utf8') as file:
13
+ languages[lang_code] = json.load(file)