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,451 @@
1
+ #!/usr/bin/env -S BACKEND_ENV=config.env python3
2
+ import copy
3
+ import sys
4
+ from datetime import datetime
5
+ from typing import Any
6
+ from uuid import UUID
7
+ from sqlalchemy import LargeBinary, text, URL
8
+ from sqlalchemy.dialects.postgresql import JSONB, ARRAY, TEXT, HSTORE
9
+ from sqlmodel import SQLModel, create_engine, Field, UniqueConstraint, Enum, Column
10
+ from sqlmodel._compat import SQLModelConfig # type: ignore
11
+ from utils.helpers import camel_case, remove_none_dict
12
+ from uuid import uuid4
13
+ from models import core
14
+ from models.enums import ResourceType, UserType, Language
15
+ from utils import regex
16
+ from utils.settings import settings
17
+ import utils.password_hashing as password_hashing
18
+
19
+
20
+ metadata = SQLModel.metadata
21
+
22
+ def get_model_from_sql_instance(db_record_type) :
23
+ match db_record_type:
24
+ case Roles.__class__:
25
+ return core.Role
26
+ case Permissions.__class__:
27
+ return core.Permission
28
+ case Users.__class__:
29
+ return core.User
30
+ case Spaces.__class__:
31
+ return core.Space
32
+ case Locks.__class__:
33
+ return core.Lock
34
+ case Attachments.__class__:
35
+ return core.Attachment
36
+ case _:
37
+ return core.Content
38
+
39
+
40
+ class Unique(SQLModel, table=False):
41
+ shortname: str = Field(regex=regex.SHORTNAME) # sa_type=String)
42
+ space_name: str = Field(regex=regex.SPACENAME)
43
+ subpath: str = Field(regex=regex.SUBPATH)
44
+ __table_args__ = (UniqueConstraint("shortname", "space_name", "subpath"),)
45
+ model_config = SQLModelConfig(form_attributes=True, populate_by_name=True, validate_assignment=True, use_enum_values=True, arbitrary_types_allowed=True) # type: ignore
46
+
47
+
48
+ class Metas(Unique, table=False):
49
+ uuid: UUID = Field(default_factory=UUID, primary_key=True)
50
+ is_active: bool = False
51
+ slug: str | None = None
52
+ displayname: dict | core.Translation | None = Field(default=None, sa_type=JSONB)
53
+ description: dict | core.Translation | None = Field(default=None, sa_type=JSONB)
54
+ tags: list[str] = Field(default_factory=dict, sa_type=JSONB)
55
+ created_at: datetime = Field(default_factory=datetime.now, index=True)
56
+ updated_at: datetime = Field(default_factory=datetime.now, index=True)
57
+ owner_shortname: str = Field(foreign_key="users.shortname")
58
+ owner_group_shortname: str | None = None
59
+ acl: list[dict[str, Any]] | list[core.ACL] | None = Field(default_factory=list, sa_type=JSONB)
60
+ payload: dict | core.Payload | None = Field(default_factory=None, sa_type=JSONB)
61
+ relationships: list[dict[str, Any]] | None = Field(default=[], sa_type=JSONB)
62
+ last_checksum_history: str | None = Field(default=None)
63
+
64
+ resource_type: str = Field()
65
+ @staticmethod
66
+ def from_record(record: core.Record, owner_shortname: str):
67
+ if record.shortname == settings.auto_uuid_rule:
68
+ record.uuid = uuid4()
69
+ record.shortname = str(record.uuid)[:8]
70
+ record.attributes["uuid"] = record.uuid
71
+
72
+ meta_class = getattr(
73
+ sys.modules["models.core"], camel_case(record.resource_type)
74
+ )
75
+
76
+ if issubclass(meta_class, core.User) and "password" in record.attributes:
77
+ hashed_pass = password_hashing.hash_password(record.attributes["password"])
78
+ record.attributes["password"] = hashed_pass
79
+
80
+ record.attributes["owner_shortname"] = owner_shortname
81
+ record.attributes["shortname"] = record.shortname
82
+ return meta_class(**remove_none_dict(record.attributes))
83
+
84
+ @staticmethod
85
+ def check_record(record: core.Record, owner_shortname: str):
86
+ meta_class = getattr(
87
+ sys.modules["models.core"], camel_case(record.resource_type)
88
+ )
89
+
90
+ meta_obj = meta_class(
91
+ owner_shortname=owner_shortname,
92
+ shortname=record.shortname,
93
+ **record.attributes,
94
+ )
95
+ return meta_obj
96
+
97
+ def update_from_record(
98
+ self, record: core.Record, old_body: dict | None = None, replace: bool = False
99
+ ) -> dict | None:
100
+ restricted_fields = [
101
+ "uuid",
102
+ "shortname",
103
+ "created_at",
104
+ "updated_at",
105
+ "owner_shortname",
106
+ "payload",
107
+ "acl",
108
+ ]
109
+ for field_name, _ in self.__dict__.items():
110
+ if field_name in record.attributes and field_name not in restricted_fields:
111
+ if isinstance(self, core.User) and field_name == "password":
112
+ self.__setattr__(
113
+ field_name,
114
+ password_hashing.hash_password(record.attributes[field_name]),
115
+ )
116
+ continue
117
+
118
+ self.__setattr__(field_name, record.attributes[field_name])
119
+
120
+ if (
121
+ not self.payload
122
+ and "payload" in record.attributes
123
+ and "content_type" in record.attributes["payload"]
124
+ ):
125
+ self.payload = core.Payload(
126
+ content_type=core.ContentType(record.attributes["payload"]["content_type"]),
127
+ schema_shortname=record.attributes["payload"].get("schema_shortname"),
128
+ body=f"{record.shortname}.json",
129
+ )
130
+
131
+ if self.payload and "payload" in record.attributes:
132
+ return self.payload.update(
133
+ payload=record.attributes["payload"], old_body=old_body, replace=replace
134
+ )
135
+ return None
136
+
137
+ def to_record(
138
+ self,
139
+ subpath: str,
140
+ shortname: str,
141
+ include: list[str] = [],
142
+ ) -> core.Record:
143
+ # Sanity check
144
+ if self.shortname != shortname:
145
+ raise Exception(
146
+ f"shortname in meta({subpath}/{self.shortname}) should be same as body({subpath}/{shortname})"
147
+ )
148
+
149
+ local_prop_list = ["uuid","resource_type","shortname","subpath"]
150
+ return core.Record(
151
+ resource_type= getattr(self, 'resource_type') if hasattr(self, 'resource_type') else get_model_from_sql_instance(self.__class__.__name__).__name__.lower(),
152
+ uuid= self.uuid,
153
+ shortname= self.shortname,
154
+ subpath= subpath,
155
+ attributes= {
156
+ key: value
157
+ for key, value in self.__dict__.items()
158
+ if key != '_sa_instance_state' and (not include or key in include) and key not in local_prop_list
159
+ }
160
+ )
161
+
162
+
163
+ class Users(Metas, table=True):
164
+ shortname: str = Field(regex=regex.SHORTNAME, unique=True)
165
+ password: str | None = None
166
+ roles: list[str] = Field(default_factory=dict, sa_type=JSONB)
167
+ groups: list[str] = Field(default_factory=dict, sa_type=JSONB)
168
+ acl: list[dict[str, Any]] | list[core.ACL] | None = Field(default_factory=list, sa_type=JSONB)
169
+ relationships: list[dict[str, Any]] | None = Field(default_factory=None, sa_type=JSONB)
170
+ type: UserType = Field(default=UserType.web)
171
+ # language: Language = Field(default=Language.en)
172
+ language: Language = Field(Column(Enum(Language)))
173
+ email: str | None = None
174
+ msisdn: str | None = Field(default=None, regex=regex.MSISDN)
175
+ locked_to_device: bool = False
176
+ is_email_verified: bool = False
177
+ is_msisdn_verified: bool = False
178
+ force_password_change: bool = True
179
+ firebase_token: str | None = None
180
+ google_id: str | None = None
181
+ facebook_id: str | None = None
182
+ social_avatar_url: str | None = None
183
+ attempt_count: int | None = None
184
+ last_login: dict | None = Field(default=None, sa_type=JSONB)
185
+ notes: str | None = None
186
+ last_checksum_history: str | None = Field(default=None)
187
+ query_policies: list[str] = Field(default=[], sa_type=ARRAY(TEXT)) # type: ignore
188
+
189
+
190
+ class Roles(Metas, table=True):
191
+ permissions: list[str] = Field(default_factory=dict, sa_type=JSONB)
192
+ owner_shortname: str = Field(foreign_key="users.shortname")
193
+ query_policies: list[str] = Field(default=[], sa_type=ARRAY(TEXT)) # type: ignore
194
+ last_checksum_history: str | None = Field(default=None)
195
+
196
+
197
+ class Permissions(Metas, table=True):
198
+ subpaths: dict = Field(default_factory=dict, sa_type=JSONB)
199
+ resource_types: list[str] = Field(default_factory=dict, sa_type=JSONB)
200
+ actions: list[str] = Field(default_factory=dict, sa_type=JSONB)
201
+ conditions: list[str] = Field(default_factory=dict, sa_type=JSONB)
202
+ restricted_fields: list[str] | None = Field(default_factory=None, sa_type=JSONB)
203
+ allowed_fields_values: dict | list[dict] | None = Field(default_factory=None, sa_type=JSONB)
204
+ filter_fields_values: str | None = None
205
+ last_checksum_history: str | None = Field(default=None)
206
+ query_policies: list[str] = Field(default=[], sa_type=ARRAY(TEXT)) # type: ignore
207
+
208
+
209
+ class Entries(Metas, table=True):
210
+ # Tickets
211
+ state: str | None = None
212
+ is_open: bool | None = None
213
+ reporter: dict | core.Reporter | None = Field(None, default_factory=None, sa_type=JSONB)
214
+ workflow_shortname: str | None = None
215
+ collaborators: dict[str, str] | None = Field(None, default_factory=None, sa_type=JSONB)
216
+ resolution_reason: str | None = None
217
+ last_checksum_history: str | None = Field(default=None)
218
+ query_policies: list[str] = Field(default=[], sa_type=ARRAY(TEXT)) # type: ignore
219
+
220
+
221
+ class Attachments(Metas, table=True):
222
+ media: bytes | None = Field(None, sa_type=LargeBinary)
223
+ body: str | None = None
224
+ state: str | None = None
225
+ last_checksum_history: str | None = Field(default=None)
226
+
227
+
228
+ class Histories(SQLModel, table=True):
229
+ uuid: UUID = Field(default_factory=UUID, primary_key=True)
230
+ request_headers: dict = Field(default_factory=dict, sa_type=JSONB)
231
+ diff: dict = Field(default_factory=dict, sa_type=JSONB)
232
+ timestamp: datetime = Field(default_factory=datetime.now)
233
+ owner_shortname: str | None = None
234
+ last_checksum_history: str | None = Field(default=None)
235
+ space_name: str = Field(regex=regex.SPACENAME)
236
+ subpath: str = Field(regex=regex.SUBPATH)
237
+ shortname: str = Field(regex=regex.SHORTNAME)
238
+
239
+ def to_record(
240
+ self,
241
+ subpath: str,
242
+ shortname: str,
243
+ include: list[str] = [],
244
+ ) -> core.Record:
245
+ # Sanity check
246
+
247
+ if self.shortname != shortname:
248
+ raise Exception(
249
+ f"shortname in meta({subpath}/{self.shortname}) should be same as body({subpath}/{shortname})"
250
+ )
251
+
252
+ record_fields = {
253
+ "resource_type": 'history',
254
+ "uuid": self.uuid,
255
+ "shortname": self.shortname,
256
+ "subpath": subpath,
257
+ }
258
+
259
+ attributes = {}
260
+ for key, value in self.__dict__.items():
261
+ if key == '_sa_instance_state':
262
+ continue
263
+ if (not include or key in include) and key not in record_fields:
264
+ attributes[key] = copy.deepcopy(value)
265
+
266
+ record_fields["attributes"] = attributes
267
+
268
+ return core.Record(**record_fields)
269
+
270
+
271
+ class Spaces(Metas, table=True):
272
+ root_registration_signature: str = ""
273
+ primary_website: str = ""
274
+ indexing_enabled: bool = False
275
+ capture_misses: bool = False
276
+ check_health: bool = False
277
+ languages: list[Language] = Field(default_factory=list, sa_type=JSONB)
278
+ icon: str = ""
279
+ mirrors: list[str] | None = Field(default_factory=None, sa_type=JSONB)
280
+ hide_folders: list[str] | None = Field(default_factory=None, sa_type=JSONB)
281
+ hide_space: bool | None = None
282
+ active_plugins: list[str] | None = Field(default_factory=None, sa_type=JSONB)
283
+ ordinal: int | None = None
284
+ last_checksum_history: str | None = Field(default=None)
285
+ query_policies: list[str] = Field(default=[], sa_type=ARRAY(TEXT)) # type: ignore
286
+
287
+
288
+ class AggregatedRecord(SQLModel, table=False):
289
+ space_name: str | None = None
290
+ subpath: str | None = None
291
+ shortname: str | None = None
292
+ resource_type: ResourceType | None = None
293
+ uuid: UUID | None = None
294
+ attributes: dict[str, Any] | None = None
295
+ attachments: dict[ResourceType, list[Any]] | None = None
296
+
297
+ # model_config = ConfigDict(extra="allow", validate_assignment=False)
298
+
299
+
300
+ class Aggregated(SQLModel, table=False):
301
+ uuid: UUID | None = None
302
+ slug: str | None = None
303
+ space_name: str | None = None
304
+ subpath: str | None = None
305
+ shortname: str | None = None
306
+ is_active: bool | None = None
307
+ displayname: dict | core.Translation | None = None
308
+ description: dict | core.Translation | None = None
309
+ tags: list[str]| None = None
310
+ created_at: datetime | None = None
311
+ updated_at: datetime | None = None
312
+ owner_shortname: str | None = None
313
+ owner_group_shortname: str | None = None
314
+ payload: dict | core.Payload | None = None
315
+ relationships: list[dict[str, Any]] | None = None
316
+ acl: list[dict[str, Any]] | None = None
317
+
318
+ resource_type: ResourceType | None = None
319
+ attributes: dict[str, Any] | None = None
320
+ attachments: dict[ResourceType, list[Any]] | None = None
321
+
322
+ __extra__: Any | None = None
323
+
324
+ # model_config = ConfigDict(extra="allow", validate_assignment=False)
325
+
326
+ def to_record(
327
+ self,
328
+ subpath: str,
329
+ shortname: str,
330
+ include: list[str] = [],
331
+ extra: dict[str, Any] | None = None,
332
+ ) -> AggregatedRecord:
333
+ record_fields = {
334
+ "resource_type": getattr(self, 'resource_type') if hasattr(self, 'resource_type') else None,
335
+ "uuid": getattr(self, 'uuid') if hasattr(self, 'uuid') else None,
336
+ "shortname": shortname,
337
+ "subpath": subpath,
338
+ }
339
+
340
+ attributes = {}
341
+
342
+ for key, value in self.__dict__.items():
343
+ if key == '_sa_instance_state':
344
+ continue
345
+ if (not include or key in include) and key not in record_fields and value is not None:
346
+ attributes[key] = copy.deepcopy(value)
347
+
348
+ record_fields["attributes"] = {
349
+ **attributes,
350
+ **(extra if extra is not None else {})
351
+ }
352
+ return AggregatedRecord(**record_fields)
353
+
354
+
355
+ class Locks(Unique, table=True):
356
+ uuid: UUID = Field(default_factory=UUID, primary_key=True)
357
+ owner_shortname: str = Field(regex=regex.SHORTNAME)
358
+ timestamp: datetime = Field(default_factory=datetime.now)
359
+ payload: dict | core.Payload | None = Field(default_factory=None, sa_type=JSONB)
360
+
361
+
362
+ class Sessions(SQLModel, table=True):
363
+ shortname: str = Field(regex=regex.SHORTNAME)
364
+ uuid: UUID = Field(default_factory=UUID, primary_key=True)
365
+ token: str = Field(...)
366
+ timestamp: datetime = Field(default_factory=datetime.now)
367
+
368
+
369
+ class Invitations(SQLModel, table=True):
370
+ uuid: UUID = Field(default_factory=UUID, primary_key=True)
371
+ invitation_token: str = Field(...)
372
+ invitation_value: str = Field(...)
373
+ timestamp: datetime = Field(default_factory=datetime.now)
374
+
375
+
376
+ class URLShorts(SQLModel, table=True):
377
+ uuid: UUID = Field(default_factory=UUID, primary_key=True)
378
+ token_uuid: str = Field(...)
379
+ url: str = Field(...)
380
+ timestamp: datetime = Field(default_factory=datetime.now)
381
+
382
+
383
+ class OTP(SQLModel, table=True):
384
+ key: str = Field(primary_key=True)
385
+ value: dict = Field(sa_type=HSTORE)
386
+ timestamp: datetime = Field(default_factory=datetime.now)
387
+
388
+
389
+ def generate_tables():
390
+ postgresql_url = URL.create(
391
+ drivername=settings.database_driver.replace('+asyncpg', '+psycopg'),
392
+ host=settings.database_host,
393
+ port=settings.database_port,
394
+ username=settings.database_username,
395
+ password=settings.database_password,
396
+ database=settings.database_name,
397
+ )
398
+ engine = create_engine(postgresql_url, echo=False)
399
+
400
+ # Enable hstore extension if it's not already enabled
401
+ with engine.connect() as conn:
402
+ conn.execute(text("CREATE EXTENSION IF NOT EXISTS hstore"))
403
+ conn.commit()
404
+
405
+ SQLModel.metadata.create_all(engine)
406
+
407
+ with engine.connect() as conn:
408
+ conn.execute(text("""
409
+ CREATE TABLE IF NOT EXISTS authz_mv_meta (
410
+ id INT PRIMARY KEY,
411
+ last_source_ts TIMESTAMPTZ,
412
+ refreshed_at TIMESTAMPTZ
413
+ )
414
+ """))
415
+ conn.execute(text("""
416
+ INSERT INTO authz_mv_meta(id, last_source_ts, refreshed_at)
417
+ VALUES (1, to_timestamp(0), now())
418
+ ON CONFLICT (id) DO NOTHING
419
+ """))
420
+
421
+ conn.execute(text("""
422
+ CREATE MATERIALIZED VIEW IF NOT EXISTS mv_user_roles AS
423
+ SELECT u.shortname AS user_shortname,
424
+ r.shortname AS role_shortname
425
+ FROM users u
426
+ JOIN LATERAL jsonb_array_elements_text(u.roles) AS role_name ON TRUE
427
+ JOIN roles r ON r.shortname = role_name
428
+ """))
429
+ conn.execute(text("""
430
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_mv_user_roles_unique
431
+ ON mv_user_roles (user_shortname, role_shortname)
432
+ """))
433
+
434
+ conn.execute(text("""
435
+ CREATE MATERIALIZED VIEW IF NOT EXISTS mv_role_permissions AS
436
+ SELECT r.shortname AS role_shortname,
437
+ p.shortname AS permission_shortname
438
+ FROM roles r
439
+ JOIN LATERAL jsonb_array_elements_text(r.permissions) AS perm_name ON TRUE
440
+ JOIN permissions p ON p.shortname = perm_name
441
+ """))
442
+ conn.execute(text("""
443
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_mv_role_permissions_unique
444
+ ON mv_role_permissions (role_shortname, permission_shortname)
445
+ """))
446
+ conn.commit()
447
+
448
+ # ALERMBIC
449
+ def init_db():
450
+ generate_tables()
451
+ print("Tables created")
@@ -0,0 +1,53 @@
1
+ import asyncio
2
+
3
+ from sqlmodel import select
4
+
5
+ from data_adapters.sql.adapter import SQLAdapter
6
+ from data_adapters.sql.create_tables import Users
7
+ from models.core import Folder
8
+ from data_adapters.adapter import data_adapter as db
9
+
10
+
11
+ async def main() -> None:
12
+ users_processed = 0
13
+ folder_processed = 0
14
+
15
+ async with SQLAdapter().get_session() as session:
16
+ statement = select(Users)
17
+ results = list((await session.execute(statement)).all())
18
+ print(f"Found {len(results)} users")
19
+ for entry in results:
20
+ entry_shortname = entry[0].shortname
21
+ folders = [
22
+ ("personal", "people", entry_shortname),
23
+ ("personal", f"people/{entry_shortname}", "notifications"),
24
+ ("personal", f"people/{entry_shortname}", "private"),
25
+ ("personal", f"people/{entry_shortname}", "protected"),
26
+ ("personal", f"people/{entry_shortname}", "public"),
27
+ ("personal", f"people/{entry_shortname}", "inbox"),
28
+ ]
29
+ try:
30
+ for folder in folders:
31
+ await db.internal_save_model(
32
+ space_name=folder[0],
33
+ subpath=folder[1],
34
+ meta=Folder(
35
+ shortname=folder[2],
36
+ is_active=True,
37
+ owner_shortname=entry_shortname
38
+ )
39
+ )
40
+ print(f"Created folder {folder} for user {entry_shortname}")
41
+ folder_processed += 1
42
+ except Exception as e:
43
+ print(f"Error creating folders for user {entry_shortname}: {e}")
44
+
45
+ users_processed += 1
46
+
47
+
48
+ print(f"\n===== DONE ====== \nScanned {users_processed} users,\
49
+ Created {folder_processed} missing folders")
50
+
51
+
52
+ if __name__ == "__main__":
53
+ asyncio.run(main())