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
main.py ADDED
@@ -0,0 +1,506 @@
1
+ #!/usr/bin/env -S BACKEND_ENV=config.env python3
2
+ """ Main module """
3
+ import socket
4
+ from starlette.datastructures import UploadFile
5
+ from contextlib import asynccontextmanager
6
+ import asyncio
7
+ import json
8
+ from os import getpid
9
+ import sys
10
+ import time
11
+ import traceback
12
+ from datetime import datetime
13
+ from typing import Any, cast
14
+ from urllib.parse import urlparse, quote
15
+ from jsonschema.exceptions import ValidationError as SchemaValidationError
16
+ from pydantic import ValidationError
17
+ from languages.loader import load_langs
18
+ from utils.middleware import CustomRequestMiddleware, ChannelMiddleware
19
+ from utils.jwt import JWTBearer
20
+ from utils.plugin_manager import plugin_manager
21
+ from fastapi import Depends, FastAPI, Request, Response, status
22
+ from utils.logger import logging_schema
23
+ from fastapi.logger import logger
24
+ from fastapi.encoders import jsonable_encoder
25
+ from fastapi.exceptions import RequestValidationError
26
+ from utils.access_control import access_control
27
+ from fastapi.responses import ORJSONResponse
28
+ from hypercorn.asyncio import serve
29
+ from hypercorn.config import Config
30
+ from starlette.concurrency import iterate_in_threadpool
31
+ from starlette.exceptions import HTTPException as StarletteHTTPException
32
+ import models.api as api
33
+ from utils.settings import settings
34
+ from asgi_correlation_id import CorrelationIdMiddleware
35
+ from data_adapters.adapter import data_adapter as db
36
+ from api.managed.router import router as managed
37
+ from api.qr.router import router as qr
38
+ from api.public.router import router as public
39
+ from api.user.router import router as user
40
+ from api.info.router import router as info, git_info
41
+ from utils.internal_error_code import InternalErrorCode
42
+
43
+ @asynccontextmanager
44
+ async def lifespan(app: FastAPI):
45
+ logger.info("Starting up")
46
+ print('{"stage":"starting up"}')
47
+
48
+ openapi_schema = app.openapi()
49
+ paths = openapi_schema["paths"]
50
+ for path in paths:
51
+ for method in paths[path]:
52
+ responses = paths[path][method]["responses"]
53
+ if responses.get("422"):
54
+ responses.pop("422")
55
+ app.openapi_schema = openapi_schema
56
+
57
+ await db.initialize_spaces()
58
+ await access_control.load_permissions_and_roles()
59
+ # await plugin_manager.load_plugins(app, capture_body)
60
+ yield
61
+
62
+
63
+ logger.info("Application shutting down")
64
+ print('{"stage":"shutting down"}')
65
+
66
+
67
+ app = FastAPI(
68
+ lifespan=lifespan,
69
+ title="Datamart API",
70
+ description="Structured Content Management System",
71
+ version=str(git_info["tag"]),
72
+ redoc_url=None,
73
+ docs_url=f"{settings.base_path}/docs",
74
+ openapi_url=f"{settings.base_path}/openapi.json",
75
+ servers=[{"url": f"{settings.base_path}/"}],
76
+ contact={
77
+ "name": "Kefah T. Issa",
78
+ "url": "https://dmart.cc",
79
+ "email": "kefah.issa@gmail.com",
80
+ },
81
+ license_info={
82
+ "name": "GNU Affero General Public License v3+",
83
+ "url": "https://www.gnu.org/licenses/agpl-3.0.en.html",
84
+ },
85
+ swagger_ui_parameters={"defaultModelsExpandDepth": -1},
86
+ openapi_tags=[
87
+ {"name": "user", "description": "User registration, login, profile and delete"},
88
+ {
89
+ "name": "managed",
90
+ "description": "Login-only content management api and media upload",
91
+ },
92
+ {
93
+ "name": "public",
94
+ "description": "Public api for query and GET access to media",
95
+ },
96
+ ],
97
+ default_response_class=ORJSONResponse,
98
+ )
99
+
100
+
101
+ async def capture_body(request: Request):
102
+ request.state.request_body = {}
103
+
104
+ if (
105
+ request.method == "POST"
106
+ and "application/json" in request.headers.get("content-type", "")
107
+ ):
108
+ request.state.request_body = await request.json()
109
+
110
+ if (
111
+ request.method == "POST"
112
+ and request.headers.get("content-type")
113
+ and "multipart/form-data" in request.headers.get("content-type", [])
114
+ ):
115
+ form = await request.form()
116
+ for field in form:
117
+ one = form[field]
118
+ if isinstance(one, str):
119
+ request.state.request_body[field] = form.get(field)
120
+ elif isinstance(one, UploadFile):
121
+ # TODO try to find a way to capture .json file content without await exeption
122
+ # inner_json= form.get(field).file
123
+ # form_to_dict[field]["file_name"]=form.get(field).filename
124
+ # form_to_dict[field]["content_type"]=form.get(field).content_type
125
+ request.state.request_body[field] = {
126
+ "name": one.filename,
127
+ "content_type": one.content_type,
128
+ }
129
+
130
+
131
+ @app.exception_handler(StarletteHTTPException)
132
+ async def my_exception_handler(_, exception):
133
+ return ORJSONResponse(content=exception.detail, status_code=exception.status_code)
134
+
135
+
136
+ @app.exception_handler(RequestValidationError)
137
+ async def validation_exception_handler(_: Request, exc: RequestValidationError):
138
+ err = jsonable_encoder({"detail": exc.errors()})["detail"]
139
+ raise api.Exception(
140
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
141
+ error=api.Error(
142
+ code=InternalErrorCode.UNPROCESSABLE_ENTITY, type="validation", message="Validation error [1]", info=err
143
+ ),
144
+ )
145
+
146
+
147
+ app.add_middleware(CustomRequestMiddleware)
148
+ app.add_middleware(ChannelMiddleware)
149
+
150
+
151
+ def set_middleware_extra(request, response, start_time, user_shortname, exception_data, response_body):
152
+ extra = {
153
+ "props": {
154
+ "timestamp": start_time,
155
+ "duration": 1000 * (time.time() - start_time),
156
+ "server": socket.gethostname(),
157
+ "process_id": getpid(),
158
+ "user_shortname": user_shortname,
159
+ "request": {
160
+ "url": request.url._url,
161
+ "verb": request.method,
162
+ "path": quote(str(request.url.path)),
163
+ "query_params": dict(request.query_params.items()),
164
+ "headers": dict(request.headers.items()),
165
+ },
166
+ "response": {
167
+ "headers": dict(response.headers.items()),
168
+ "http_status": response.status_code,
169
+ },
170
+ }
171
+ }
172
+
173
+ if exception_data is not None:
174
+ extra["props"]["exception"] = exception_data
175
+
176
+ if hasattr(request.state, "request_body"):
177
+ extra["props"]["request"]["body"] = request.state.request_body
178
+
179
+ if response_body and isinstance(response_body, dict):
180
+ extra["props"]["response"]["body"] = response_body
181
+
182
+ return extra
183
+
184
+
185
+
186
+ def set_middleware_response_headers(request, response):
187
+ referer = request.headers.get(
188
+ "referer",
189
+ request.headers.get("origin",
190
+ request.headers.get("x-forwarded-proto", "http")
191
+ + "://"
192
+ + request.headers.get(
193
+ "x-forwarded-host", f"{settings.listening_host}:{settings.listening_port}"
194
+ )),
195
+ )
196
+ origin = urlparse(referer)
197
+ response.headers[
198
+ "Access-Control-Allow-Origin"
199
+ ] = f"{origin.scheme}://{origin.netloc}"
200
+
201
+
202
+ response.headers["Access-Control-Allow-Credentials"] = "true"
203
+ response.headers["Access-Control-Allow-Headers"] = "content-type, charset, authorization, accept-language, content-length"
204
+ response.headers["Access-Control-Max-Age"] = "600"
205
+ response.headers[
206
+ "Access-Control-Allow-Methods"
207
+ ] = "OPTIONS, DELETE, POST, GET, PATCH, PUT"
208
+
209
+ response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
210
+ response.headers["Pragma"] = "no-cache"
211
+ response.headers["Expires"] = "0"
212
+ response.headers["x-server-time"] = datetime.now().isoformat()
213
+ response.headers["Access-Control-Expose-Headers"] = "x-server-time"
214
+ return response
215
+
216
+
217
+ def mask_sensitive_data(data):
218
+ if isinstance(data, dict):
219
+ return {k: mask_sensitive_data(v) if k not in ['password', 'access_token', 'refresh_token', 'auth_token', 'jwt', 'otp', 'code', 'token'] else '******' for k, v in data.items()}
220
+ elif isinstance(data, list):
221
+ return [mask_sensitive_data(item) for item in data]
222
+ elif isinstance(data, str) and 'auth_token' in data:
223
+ return '******'
224
+ return data
225
+
226
+
227
+ def set_logging(response, extra, request, exception_data):
228
+ _extra = mask_sensitive_data(extra)
229
+ if isinstance(_extra, dict):
230
+ if 400 <= response.status_code < 500:
231
+ logger.warning("Served request", extra=_extra)
232
+ elif response.status_code >= 500 or exception_data is not None:
233
+ logger.error("Served request", extra=_extra)
234
+ elif request.method != "OPTIONS": # Do not log OPTIONS request, to reduce excessive logging
235
+ logger.info("Served request", extra=_extra)
236
+
237
+
238
+ def set_stack(e):
239
+ return [
240
+ {
241
+ "file": frame.f_code.co_filename,
242
+ "function": frame.f_code.co_name,
243
+ "line": lineno,
244
+ }
245
+ for frame, lineno in traceback.walk_tb(e.__traceback__)
246
+ if "site-packages" not in frame.f_code.co_filename
247
+ ]
248
+
249
+ @app.middleware("http")
250
+ async def middle(request: Request, call_next):
251
+ """Wrapper function to manage errors and logging"""
252
+ if (
253
+ request.url._url.endswith("/docs")
254
+ or request.url._url.endswith("openapi.json")
255
+ or request.url._url.endswith("/favicon.ico")
256
+ ):
257
+ return await call_next(request)
258
+
259
+ start_time = time.time()
260
+ response_body: str | dict = {}
261
+ exception_data: dict[str, Any] | None = None
262
+
263
+
264
+ try:
265
+ response = await asyncio.wait_for(call_next(request), timeout=settings.request_timeout)
266
+ raw_response = [section async for section in response.body_iterator]
267
+ response.body_iterator = iterate_in_threadpool(iter(raw_response))
268
+ raw_data = b"".join(raw_response)
269
+ if raw_data:
270
+ try:
271
+ response_body = json.loads(raw_data)
272
+ except Exception:
273
+ response_body = {}
274
+ except asyncio.TimeoutError:
275
+ response = ORJSONResponse(content={'status':'failed',
276
+ 'error': {"code":504, "message": 'Request processing time excedeed limit'}},
277
+ status_code=status.HTTP_504_GATEWAY_TIMEOUT)
278
+ response_body = json.loads(str(response.body, 'utf8'))
279
+ except api.Exception as e:
280
+ if settings.active_data_db == 'sql':
281
+ if e.error.message.startswith('(sqlalchemy.dialects.postgresql'):
282
+ response = ORJSONResponse(
283
+ status_code=500,
284
+ content={
285
+ "status": "failed",
286
+ "error": 'Something went wrong',
287
+ },
288
+ )
289
+ else:
290
+ response = ORJSONResponse(
291
+ status_code=e.status_code,
292
+ content=jsonable_encoder(
293
+ api.Response(status=api.Status.failed, error=e.error)
294
+ ),
295
+ )
296
+ else:
297
+ response = ORJSONResponse(
298
+ status_code=e.status_code,
299
+ content=jsonable_encoder(
300
+ api.Response(status=api.Status.failed, error=e.error)
301
+ ),
302
+ )
303
+ stack = set_stack(e)
304
+ exception_data = {"props": {"exception": str(e), "stack": stack}}
305
+ response_body = json.loads(str(response.body, 'utf8'))
306
+ except ValidationError as e:
307
+ stack = set_stack(e)
308
+ exception_data = {"props": {"exception": str(e), "stack": stack}}
309
+ response = ORJSONResponse(
310
+ status_code=422,
311
+ content={
312
+ "status": "failed",
313
+ "error": {
314
+ "type": "validation",
315
+ "code": 422,
316
+ "message": "Validation error [2]",
317
+ "info": jsonable_encoder(e.errors()),
318
+ },
319
+ },
320
+ )
321
+ response_body = json.loads(str(response.body, 'utf8'))
322
+ except SchemaValidationError as e:
323
+ stack = set_stack(e)
324
+ exception_data = {"props": {"exception": str(e), "stack": stack}}
325
+ response = ORJSONResponse(
326
+ status_code=400,
327
+ content={
328
+ "status": "failed",
329
+ "error": {
330
+ "type": "validation",
331
+ "code": 422,
332
+ "message": "Validation error [3]",
333
+ "info": [{
334
+ "loc": list(e.path),
335
+ "msg": e.message
336
+ }],
337
+ },
338
+ },
339
+ )
340
+ response_body = json.loads(str(response.body, 'utf8'))
341
+ except Exception:
342
+ exception_message = ""
343
+ stack = None
344
+ if ee := sys.exc_info()[1]:
345
+ stack = set_stack(ee)
346
+ exception_message = str(ee)
347
+ exception_data = {"props": {"exception": str(ee), "stack": stack}}
348
+
349
+ error_log = {"type": "general", "code": 99, "message": exception_message}
350
+ if settings.debug_enabled:
351
+ error_log["stack"] = stack
352
+ response = ORJSONResponse(
353
+ status_code=500,
354
+ content={
355
+ "status": "failed",
356
+ "error": error_log,
357
+ },
358
+ )
359
+ response_body = json.loads(str(response.body, 'utf8'))
360
+
361
+ response = set_middleware_response_headers(request, response)
362
+
363
+ user_shortname = "guest"
364
+ if request.url.path == "/user/login":
365
+ try:
366
+ body = getattr(request.state, "request_body", {}) or {}
367
+ if isinstance(body, dict):
368
+ shortname_value = body.get("shortname")
369
+ if isinstance(shortname_value, str) and shortname_value.strip():
370
+ user_shortname = shortname_value
371
+ except Exception:
372
+ pass
373
+ else:
374
+ try:
375
+ user_shortname = str(await JWTBearer().__call__(request))
376
+ except Exception:
377
+ user_shortname = "guest"
378
+
379
+
380
+ extra = set_middleware_extra(request, response, start_time, user_shortname, exception_data, response_body)
381
+
382
+ set_logging(response, extra, request, exception_data)
383
+
384
+ #TODO: CHECK THIS
385
+ # if settings.hide_stack_trace:
386
+ # if (
387
+ # response_body and isinstance(response_body, dict)
388
+ # and "error" in response_body
389
+ # and "stack" in response_body["error"]
390
+ # ):
391
+ # response_body["error"].pop("stack", None)
392
+ #
393
+ # response.body_iterator = iterate_in_threadpool(iter([json.dumps(response_body).encode("utf-8")]))
394
+
395
+ return response
396
+
397
+
398
+ app.add_middleware(
399
+ CorrelationIdMiddleware,
400
+ header_name='X-Correlation-ID',
401
+ update_request_header=False,
402
+ validator=None,
403
+ )
404
+
405
+
406
+ @app.get("/", include_in_schema=False)
407
+ async def root():
408
+ """Dummy api end point"""
409
+ return {"status": "success", "message": "DMART API"}
410
+
411
+
412
+ # @app.get("/s", include_in_schema=False)
413
+ # async def secrets(key):
414
+ # if key == "alpha":
415
+ # return settings.dict()
416
+
417
+ """
418
+ @app.get("/spaces-backup", include_in_schema=False)
419
+ async def space_backup(key: str):
420
+ if not key or key != "ABC":
421
+ return api.Response(
422
+ status=api.Status.failed,
423
+ error=api.Error(type="git", code=InternalErrorCode.INVALID_APP_KEY, message="Api key is invalid"),
424
+ )
425
+
426
+ import subprocess
427
+
428
+ cmd = "/usr/bin/bash -c 'cd .. && ./spaces-backup.sh'"
429
+ # cmd = "../git-update.sh"
430
+
431
+ result_stdout, result_stderr = subprocess.Popen(
432
+ cmd.split(" "), stdout=subprocess.PIPE, stderr=subprocess.PIPE
433
+ ).communicate()
434
+ attributes = {
435
+ "stdout": result_stdout.decode().split("\n"),
436
+ "stderr": result_stderr.decode().split("\n"),
437
+ }
438
+ return api.Response(status=api.Status.success, attributes=attributes)
439
+ """
440
+
441
+ app.include_router(
442
+ user, prefix="/user", tags=["user"], dependencies=[Depends(capture_body)]
443
+ )
444
+ app.include_router(
445
+ managed, prefix="/managed", tags=["managed"], dependencies=[Depends(capture_body)]
446
+ )
447
+ app.include_router(
448
+ qr,
449
+ prefix="/qr",
450
+ tags=["QR"],
451
+ dependencies=[Depends(capture_body)],
452
+ )
453
+
454
+ app.include_router(
455
+ public, prefix="/public", tags=["public"], dependencies=[Depends(capture_body)]
456
+ )
457
+
458
+ app.include_router(
459
+ info, prefix="/info", tags=["info"], dependencies=[Depends(capture_body)]
460
+ )
461
+
462
+ # load plugins
463
+ asyncio.run(plugin_manager.load_plugins(app, capture_body))
464
+
465
+
466
+ @app.options("/{x:path}", include_in_schema=False)
467
+ async def myoptions():
468
+ return Response(status_code=status.HTTP_200_OK)
469
+
470
+
471
+ @app.get("/{x:path}", include_in_schema=False)
472
+ @app.post("/{x:path}", include_in_schema=False)
473
+ @app.put("/{x:path}", include_in_schema=False)
474
+ @app.patch("/{x:path}", include_in_schema=False)
475
+ @app.delete("/{x:path}", include_in_schema=False)
476
+ async def catchall() -> None:
477
+ raise api.Exception(
478
+ status_code=status.HTTP_404_NOT_FOUND,
479
+ error=api.Error(
480
+ type="catchall", code=InternalErrorCode.INVALID_ROUTE, message="Requested method or path is invalid"
481
+ ),
482
+ )
483
+
484
+ load_langs()
485
+
486
+
487
+ async def main():
488
+ config = Config()
489
+ config.bind = [f"{settings.listening_host}:{settings.listening_port}"]
490
+ config.backlog = 200
491
+
492
+ config.logconfig_dict = logging_schema
493
+ config.errorlog = logger
494
+
495
+ try:
496
+ await serve(cast(Any, app), config)
497
+ except OSError as e:
498
+ print("[!1server]", e)
499
+
500
+
501
+ if __name__ == "__main__":
502
+ try:
503
+ asyncio.run(main())
504
+ except Exception as e:
505
+ print("[!1server]", e)
506
+
migrate.py ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env -S BACKEND_ENV=config.env python3
2
+ import asyncio
3
+ import sys
4
+
5
+
6
+ def main():
7
+ if len(sys.argv) < 2:
8
+ print("Usage: migrate.py <json_to_db|db_to_json>")
9
+ sys.exit(1)
10
+
11
+ command = sys.argv[1]
12
+
13
+ if command == "json_to_db":
14
+ import data_adapters.sql.json_to_db_migration as json_to_db_migration
15
+ asyncio.run(json_to_db_migration.main())
16
+ elif command == "db_to_json":
17
+ import data_adapters.sql.db_to_json_migration as db_to_json_migration
18
+ db_to_json_migration.main()
19
+ else:
20
+ print("Invalid command. Use 'json_to_db' or 'db_to_json'.")
21
+ sys.exit(1)
22
+
23
+ if __name__ == "__main__":
24
+ main()
models/__init__.py ADDED
File without changes