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.
- alembic/__init__.py +0 -0
- alembic/env.py +91 -0
- alembic/scripts/__init__.py +0 -0
- alembic/scripts/calculate_checksums.py +77 -0
- alembic/scripts/migration_f7a4949eed19.py +28 -0
- alembic/versions/0f3d2b1a7c21_add_authz_materialized_views.py +87 -0
- alembic/versions/10d2041b94d4_last_checksum_history.py +62 -0
- alembic/versions/1cf4e1ee3cb8_ext_permission_with_filter_fields_values.py +33 -0
- alembic/versions/26bfe19b49d4_rm_failedloginattempts.py +42 -0
- alembic/versions/3c8bca2219cc_add_otp_table.py +38 -0
- alembic/versions/6675fd9dfe42_remove_unique_from_sessions_table.py +36 -0
- alembic/versions/71bc1df82e6a_adding_user_last_login_at.py +43 -0
- alembic/versions/74288ccbd3b5_initial.py +264 -0
- alembic/versions/7520a89a8467_rm_activesession_table.py +39 -0
- alembic/versions/848b623755a4_make_created_nd_updated_at_required.py +138 -0
- alembic/versions/8640dcbebf85_add_notes_to_users.py +32 -0
- alembic/versions/91c94250232a_adding_fk_on_owner_shortname.py +104 -0
- alembic/versions/98ecd6f56f9a_ext_meta_with_owner_group_shortname.py +66 -0
- alembic/versions/9aae9138c4ef_indexing_created_at_updated_at.py +80 -0
- alembic/versions/__init__.py +0 -0
- alembic/versions/b53f916b3f6d_json_to_jsonb.py +492 -0
- alembic/versions/eb5f1ec65156_adding_user_locked_to_device.py +36 -0
- alembic/versions/f7a4949eed19_adding_query_policies_to_meta.py +60 -0
- api/__init__.py +0 -0
- api/info/__init__.py +0 -0
- api/info/router.py +109 -0
- api/managed/__init__.py +0 -0
- api/managed/router.py +1541 -0
- api/managed/utils.py +1850 -0
- api/public/__init__.py +0 -0
- api/public/router.py +758 -0
- api/qr/__init__.py +0 -0
- api/qr/router.py +108 -0
- api/user/__init__.py +0 -0
- api/user/model/__init__.py +0 -0
- api/user/model/errors.py +14 -0
- api/user/model/requests.py +165 -0
- api/user/model/responses.py +11 -0
- api/user/router.py +1401 -0
- api/user/service.py +270 -0
- bundler.py +44 -0
- config/__init__.py +0 -0
- config/channels.json +11 -0
- config/notification.json +17 -0
- data_adapters/__init__.py +0 -0
- data_adapters/adapter.py +16 -0
- data_adapters/base_data_adapter.py +467 -0
- data_adapters/file/__init__.py +0 -0
- data_adapters/file/adapter.py +2043 -0
- data_adapters/file/adapter_helpers.py +1013 -0
- data_adapters/file/archive.py +150 -0
- data_adapters/file/create_index.py +331 -0
- data_adapters/file/create_users_folders.py +52 -0
- data_adapters/file/custom_validations.py +68 -0
- data_adapters/file/drop_index.py +40 -0
- data_adapters/file/health_check.py +560 -0
- data_adapters/file/redis_services.py +1110 -0
- data_adapters/helpers.py +27 -0
- data_adapters/sql/__init__.py +0 -0
- data_adapters/sql/adapter.py +3210 -0
- data_adapters/sql/adapter_helpers.py +491 -0
- data_adapters/sql/create_tables.py +451 -0
- data_adapters/sql/create_users_folders.py +53 -0
- data_adapters/sql/db_to_json_migration.py +482 -0
- data_adapters/sql/health_check_sql.py +232 -0
- data_adapters/sql/json_to_db_migration.py +454 -0
- data_adapters/sql/update_query_policies.py +101 -0
- data_generator.py +81 -0
- dmart-0.1.9.dist-info/METADATA +64 -0
- dmart-0.1.9.dist-info/RECORD +149 -0
- dmart-0.1.9.dist-info/WHEEL +5 -0
- dmart-0.1.9.dist-info/entry_points.txt +2 -0
- dmart-0.1.9.dist-info/top_level.txt +23 -0
- dmart.py +513 -0
- get_settings.py +7 -0
- languages/__init__.py +0 -0
- languages/arabic.json +15 -0
- languages/english.json +16 -0
- languages/kurdish.json +14 -0
- languages/loader.py +13 -0
- main.py +506 -0
- migrate.py +24 -0
- models/__init__.py +0 -0
- models/api.py +203 -0
- models/core.py +597 -0
- models/enums.py +255 -0
- password_gen.py +8 -0
- plugins/__init__.py +0 -0
- plugins/action_log/__init__.py +0 -0
- plugins/action_log/plugin.py +121 -0
- plugins/admin_notification_sender/__init__.py +0 -0
- plugins/admin_notification_sender/plugin.py +124 -0
- plugins/ldap_manager/__init__.py +0 -0
- plugins/ldap_manager/plugin.py +100 -0
- plugins/local_notification/__init__.py +0 -0
- plugins/local_notification/plugin.py +123 -0
- plugins/realtime_updates_notifier/__init__.py +0 -0
- plugins/realtime_updates_notifier/plugin.py +58 -0
- plugins/redis_db_update/__init__.py +0 -0
- plugins/redis_db_update/plugin.py +188 -0
- plugins/resource_folders_creation/__init__.py +0 -0
- plugins/resource_folders_creation/plugin.py +81 -0
- plugins/system_notification_sender/__init__.py +0 -0
- plugins/system_notification_sender/plugin.py +188 -0
- plugins/update_access_controls/__init__.py +0 -0
- plugins/update_access_controls/plugin.py +9 -0
- pytests/__init__.py +0 -0
- pytests/api_user_models_erros_test.py +16 -0
- pytests/api_user_models_requests_test.py +98 -0
- pytests/archive_test.py +72 -0
- pytests/base_test.py +300 -0
- pytests/get_settings_test.py +14 -0
- pytests/json_to_db_migration_test.py +237 -0
- pytests/service_test.py +26 -0
- pytests/test_info.py +55 -0
- pytests/test_status.py +15 -0
- run_notification_campaign.py +98 -0
- scheduled_notification_handler.py +121 -0
- schema_migration.py +208 -0
- schema_modulate.py +192 -0
- set_admin_passwd.py +55 -0
- sync.py +202 -0
- utils/__init__.py +0 -0
- utils/access_control.py +306 -0
- utils/async_request.py +8 -0
- utils/exporter.py +309 -0
- utils/firebase_notifier.py +57 -0
- utils/generate_email.py +38 -0
- utils/helpers.py +352 -0
- utils/hypercorn_config.py +12 -0
- utils/internal_error_code.py +60 -0
- utils/jwt.py +124 -0
- utils/logger.py +167 -0
- utils/middleware.py +99 -0
- utils/notification.py +75 -0
- utils/password_hashing.py +16 -0
- utils/plugin_manager.py +215 -0
- utils/query_policies_helper.py +112 -0
- utils/regex.py +44 -0
- utils/repository.py +529 -0
- utils/router_helper.py +19 -0
- utils/settings.py +165 -0
- utils/sms_notifier.py +21 -0
- utils/social_sso.py +67 -0
- utils/templates/activation.html.j2 +26 -0
- utils/templates/reminder.html.j2 +17 -0
- utils/ticket_sys_utils.py +203 -0
- utils/web_notifier.py +29 -0
- websocket.py +231 -0
|
@@ -0,0 +1,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())
|