dmart 0.1.6__py3-none-any.whl → 0.1.8__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 (48) hide show
  1. alembic/scripts/__init__.py +0 -0
  2. alembic/scripts/calculate_checksums.py +77 -0
  3. alembic/scripts/migration_f7a4949eed19.py +28 -0
  4. alembic/versions/0f3d2b1a7c21_add_authz_materialized_views.py +87 -0
  5. alembic/versions/10d2041b94d4_last_checksum_history.py +62 -0
  6. alembic/versions/1cf4e1ee3cb8_ext_permission_with_filter_fields_values.py +33 -0
  7. alembic/versions/26bfe19b49d4_rm_failedloginattempts.py +42 -0
  8. alembic/versions/3c8bca2219cc_add_otp_table.py +38 -0
  9. alembic/versions/6675fd9dfe42_remove_unique_from_sessions_table.py +36 -0
  10. alembic/versions/71bc1df82e6a_adding_user_last_login_at.py +43 -0
  11. alembic/versions/74288ccbd3b5_initial.py +264 -0
  12. alembic/versions/7520a89a8467_rm_activesession_table.py +39 -0
  13. alembic/versions/848b623755a4_make_created_nd_updated_at_required.py +138 -0
  14. alembic/versions/8640dcbebf85_add_notes_to_users.py +32 -0
  15. alembic/versions/91c94250232a_adding_fk_on_owner_shortname.py +104 -0
  16. alembic/versions/98ecd6f56f9a_ext_meta_with_owner_group_shortname.py +66 -0
  17. alembic/versions/9aae9138c4ef_indexing_created_at_updated_at.py +80 -0
  18. alembic/versions/__init__.py +0 -0
  19. alembic/versions/b53f916b3f6d_json_to_jsonb.py +492 -0
  20. alembic/versions/eb5f1ec65156_adding_user_locked_to_device.py +36 -0
  21. alembic/versions/f7a4949eed19_adding_query_policies_to_meta.py +60 -0
  22. api/user/model/__init__.py +0 -0
  23. api/user/model/errors.py +14 -0
  24. api/user/model/requests.py +165 -0
  25. api/user/model/responses.py +11 -0
  26. {dmart-0.1.6.dist-info → dmart-0.1.8.dist-info}/METADATA +1 -1
  27. {dmart-0.1.6.dist-info → dmart-0.1.8.dist-info}/RECORD +48 -5
  28. plugins/action_log/__init__.py +0 -0
  29. plugins/action_log/plugin.py +121 -0
  30. plugins/admin_notification_sender/__init__.py +0 -0
  31. plugins/admin_notification_sender/plugin.py +124 -0
  32. plugins/ldap_manager/__init__.py +0 -0
  33. plugins/ldap_manager/plugin.py +100 -0
  34. plugins/local_notification/__init__.py +0 -0
  35. plugins/local_notification/plugin.py +123 -0
  36. plugins/realtime_updates_notifier/__init__.py +0 -0
  37. plugins/realtime_updates_notifier/plugin.py +58 -0
  38. plugins/redis_db_update/__init__.py +0 -0
  39. plugins/redis_db_update/plugin.py +188 -0
  40. plugins/resource_folders_creation/__init__.py +0 -0
  41. plugins/resource_folders_creation/plugin.py +81 -0
  42. plugins/system_notification_sender/__init__.py +0 -0
  43. plugins/system_notification_sender/plugin.py +188 -0
  44. plugins/update_access_controls/__init__.py +0 -0
  45. plugins/update_access_controls/plugin.py +9 -0
  46. {dmart-0.1.6.dist-info → dmart-0.1.8.dist-info}/WHEEL +0 -0
  47. {dmart-0.1.6.dist-info → dmart-0.1.8.dist-info}/entry_points.txt +0 -0
  48. {dmart-0.1.6.dist-info → dmart-0.1.8.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,14 @@
1
+ from models.api import Error
2
+ from utils.internal_error_code import InternalErrorCode
3
+
4
+ INVALID_OTP = Error(
5
+ type="OTP",
6
+ code=InternalErrorCode.OTP_INVALID,
7
+ message="Invalid OTP",
8
+ )
9
+
10
+ EXPIRED_OTP = Error(
11
+ type="OTP",
12
+ code=InternalErrorCode.OTP_EXPIRED,
13
+ message="Expired OTP",
14
+ )
@@ -0,0 +1,165 @@
1
+ from typing import Dict
2
+ from pydantic import BaseModel, Field
3
+ from utils.internal_error_code import InternalErrorCode
4
+ import utils.regex as rgx
5
+ from models.api import Exception, Error
6
+ from models.enums import StrEnum
7
+
8
+
9
+ class OTPType(StrEnum):
10
+ SMS = "SMS"
11
+ EMAIL = "EMAIL"
12
+
13
+
14
+ class SendOTPRequest(BaseModel):
15
+ shortname: str | None = Field(None, pattern=rgx.SHORTNAME)
16
+ msisdn: str | None = Field(None, pattern=rgx.MSISDN)
17
+ email: str | None = Field(None, pattern=rgx.EMAIL)
18
+
19
+ def check_fields(self) -> Dict[str, str]:
20
+ if self.email is None and self.msisdn is None and self.shortname is None:
21
+ raise Exception(
22
+ 422,
23
+ Error(
24
+ type="OTP",
25
+ code=InternalErrorCode.EMAIL_OR_MSISDN_REQUIRED,
26
+ message="One of these [email, msisdn, shortname] should be set!",
27
+ ),
28
+ )
29
+
30
+ if [self.email, self.msisdn, self.shortname].count(None) != 2:
31
+ raise Exception(
32
+ 422,
33
+ Error(
34
+ type="OTP",
35
+ code=InternalErrorCode.INVALID_STANDALONE_DATA,
36
+ message="Too many input has been passed",
37
+ ),
38
+ )
39
+
40
+ elif self.msisdn:
41
+ return {"msisdn": self.msisdn}
42
+ elif self.email:
43
+ return {"email": self.email}
44
+ elif self.shortname:
45
+ return {"shortname": self.shortname}
46
+
47
+ raise Exception(
48
+ 500,
49
+ Error(
50
+ type="OTP",
51
+ code=InternalErrorCode.OTP_ISSUE,
52
+ message="Something went wrong",
53
+ ),
54
+ )
55
+
56
+ model_config = {
57
+ "json_schema_extra": {
58
+ "examples": [
59
+ {
60
+ "msisdn": "7777778110"
61
+ }
62
+ ]
63
+ }
64
+ }
65
+
66
+ class PasswordResetRequest(BaseModel):
67
+ msisdn: str | None = Field(None, pattern=rgx.MSISDN)
68
+ shortname: str | None = Field(None, pattern=rgx.SHORTNAME)
69
+ email: str | None = Field(None, pattern=rgx.EMAIL)
70
+
71
+ def check_fields(self) -> Dict[str, str]:
72
+ if self.email is None and self.msisdn is None and self.shortname is None:
73
+ raise Exception(
74
+ 422,
75
+ Error(
76
+ type="OTP",
77
+ code=InternalErrorCode.EMAIL_OR_MSISDN_REQUIRED,
78
+ message="One of these [shortname, email, msisdn] should be set!",
79
+ ),
80
+ )
81
+
82
+ if [self.email, self.msisdn, self.shortname].count(None) != 2:
83
+ raise Exception(
84
+ 422,
85
+ Error(
86
+ type="OTP",
87
+ code=InternalErrorCode.INVALID_STANDALONE_DATA,
88
+ message="Too many input has been passed",
89
+ ),
90
+ )
91
+
92
+ elif self.msisdn:
93
+ return {"msisdn": self.msisdn}
94
+ elif self.email:
95
+ return {"email": self.email}
96
+ elif self.shortname:
97
+ return {"shortname": self.shortname}
98
+
99
+ raise Exception(
100
+ 500,
101
+ Error(
102
+ type="password_reset",
103
+ code=InternalErrorCode.PASSWORD_RESET_ERROR,
104
+ message="Something went wrong",
105
+ ),
106
+ )
107
+
108
+ model_config = {
109
+ "json_schema_extra": {
110
+ "examples": [
111
+ {
112
+ "msisdn": "7777778110"
113
+ }
114
+ ]
115
+ }
116
+ }
117
+ class ConfirmOTPRequest(SendOTPRequest, BaseModel):
118
+ code: str = Field(..., pattern=rgx.OTP_CODE)
119
+
120
+ model_config = {
121
+ "json_schema_extra": {
122
+ "examples": [
123
+ {
124
+ "code": "84293201"
125
+ }
126
+ ]
127
+ }
128
+ }
129
+
130
+ class UserLoginRequest(BaseModel):
131
+ shortname: str | None = Field(None, pattern=rgx.SHORTNAME)
132
+ email: str | None = Field(None, pattern=rgx.EMAIL)
133
+ msisdn: str | None = Field(None, pattern=rgx.MSISDN)
134
+ password: str | None = Field(None)
135
+ invitation: str | None = Field(None, pattern=rgx.INVITATION)
136
+ firebase_token: str | None = Field(None)
137
+ otp: str | None = Field(None)
138
+
139
+ def check_fields(self) -> Dict[str, str] | None:
140
+ if self.shortname is None and self.email is None and self.msisdn is None:
141
+ return {}
142
+ # raise ValueError("One of these [shortname, email, msisdn] should be set!")
143
+
144
+ if [self.shortname, self.email, self.msisdn].count(None) != 2:
145
+ raise ValueError("Too many input has been passed")
146
+
147
+ if self.shortname:
148
+ return {"shortname": self.shortname}
149
+ elif self.msisdn:
150
+ return {"msisdn": self.msisdn}
151
+ elif self.email:
152
+ return {"email": self.email}
153
+
154
+ return None
155
+
156
+ model_config = {
157
+ "json_schema_extra": {
158
+ "examples": [
159
+ {
160
+ "shortname": "john_doo",
161
+ "password": "my_secure_password_@_93301"
162
+ }
163
+ ]
164
+ }
165
+ }
@@ -0,0 +1,11 @@
1
+ from pydantic import BaseModel, Field
2
+
3
+ from models.api import Response
4
+
5
+
6
+ class Confirmation(BaseModel):
7
+ confirmation: str = Field(...)
8
+
9
+
10
+ class ConfirmationResponse(Response):
11
+ data: Confirmation
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dmart
3
- Version: 0.1.6
3
+ Version: 0.1.8
4
4
  Requires-Python: >=3.10
5
5
  Requires-Dist: fastapi
6
6
  Requires-Dist: pydantic
@@ -14,6 +14,27 @@ sync.py,sha256=FlmubtlnFaxtZkbRV1-eyS_Sx5KBRvWyIZjvd0Tiar4,7339
14
14
  websocket.py,sha256=Q8WUTvOTBHKP5xy5wim8yn0t-BfjrPwx7J_6vbzAm1A,7576
15
15
  alembic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  alembic/env.py,sha256=z12UKhorKSOKEovOCQOwRjfR_tup4VeRlhcB1UPk3Xw,2700
17
+ alembic/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ alembic/scripts/calculate_checksums.py,sha256=v2NLEvReA9V3noJE-BWANgKDdhc8Mqg1ZmJJ8nc8sGI,3443
19
+ alembic/scripts/migration_f7a4949eed19.py,sha256=oUXuxjU4MbVafm4S-xu5J_4apHqW6hQZ8ftpJtCtM28,1462
20
+ alembic/versions/0f3d2b1a7c21_add_authz_materialized_views.py,sha256=70vrPfhMHVHhw1l7KNQpvCUU76XZfvGcPlrXOeU4FHU,2599
21
+ alembic/versions/10d2041b94d4_last_checksum_history.py,sha256=FYjyzE5Xi6bn9LUtxLKFnQOKhg4GZOrTkpqtmlCnY6Y,2860
22
+ alembic/versions/1cf4e1ee3cb8_ext_permission_with_filter_fields_values.py,sha256=taaFTkTaFfLfj7QUUfIdcojvbOdjut_dWtrIvQUN7-4,921
23
+ alembic/versions/26bfe19b49d4_rm_failedloginattempts.py,sha256=-Roftn8OSUz7kfR3yMI02rulyvnH46W0WpJfDQ5xAdk,1471
24
+ alembic/versions/3c8bca2219cc_add_otp_table.py,sha256=f-YSxx1iLA0iHWukq1VnngFmiiYQyEzowicox0-wtbY,1125
25
+ alembic/versions/6675fd9dfe42_remove_unique_from_sessions_table.py,sha256=yP40IHwtzFAAUid_VeeCkt_9F2RszMYBjNKx2mDTKNw,1037
26
+ alembic/versions/71bc1df82e6a_adding_user_last_login_at.py,sha256=LINO2hWUEFmsTspupd8AxgUS6tNJFNgGC5do1QPMQ1E,1486
27
+ alembic/versions/74288ccbd3b5_initial.py,sha256=hZ1w5mqVKSO13J1O4zcTccDJBk2c-cmIB0AUx1KLlLI,13988
28
+ alembic/versions/7520a89a8467_rm_activesession_table.py,sha256=4VYv9tCXkTQNaXPVvjdsKTiw093KkBBUHNSiWdZtqtA,1305
29
+ alembic/versions/848b623755a4_make_created_nd_updated_at_required.py,sha256=o106xd-apT8ZAXdVCjX76o7VuqRQNkIl0V4f41m5j2E,5375
30
+ alembic/versions/8640dcbebf85_add_notes_to_users.py,sha256=XjfW2Lc0-xoRGB2qfdzDstw7Caaev6JhqRNYV_sIqgc,813
31
+ alembic/versions/91c94250232a_adding_fk_on_owner_shortname.py,sha256=F9FDF2qyeqvOx1gkYq5HQMkVCH1QrXciYSHVxijsSSU,4024
32
+ alembic/versions/98ecd6f56f9a_ext_meta_with_owner_group_shortname.py,sha256=S466VE3jsxXDEpTKyhIN06uutM0jI7zYHTWIYvU_pJw,2579
33
+ alembic/versions/9aae9138c4ef_indexing_created_at_updated_at.py,sha256=xrqfJB5cER0PYi1torwkgsbBBjDeeM6dyj4pu-quphk,3662
34
+ alembic/versions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
+ alembic/versions/b53f916b3f6d_json_to_jsonb.py,sha256=LPnevQjbjs0KrwqxYHtGUC6mSBBsUpiCBnwD-FN2x50,24840
36
+ alembic/versions/eb5f1ec65156_adding_user_locked_to_device.py,sha256=4U45sfMGIBMcRkwPuFPRc_M9pL2lmOn9kguWjakPAbU,1007
37
+ alembic/versions/f7a4949eed19_adding_query_policies_to_meta.py,sha256=LA4rx3u0Ei5m4OcSsVYHBsGMeKOJdx8G88yK1kBLFys,2307
17
38
  api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
39
  api/info/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
40
  api/info/router.py,sha256=sQZZor7A-uDzsJX39aqEA7bMZOJ-WTitYeFvVNWfaHw,3938
@@ -27,6 +48,10 @@ api/qr/router.py,sha256=Ru7UT_iQS6mFwE1bCPPrusSQfFgoV_u6pjZJ0gArE7g,3870
27
48
  api/user/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
49
  api/user/router.py,sha256=FwuxM04niGlls-V1MPzHH5A4jHJ0g5NNIFZBE6bJ9Fc,51302
29
50
  api/user/service.py,sha256=-iQpcBVPTDiLE_xOf87Ni0oSQDtmALAXEwU4IgSvnJk,8463
51
+ api/user/model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
+ api/user/model/errors.py,sha256=rhcIcbAIk_7rs5lxdgcsf7SWFGmC9QLsgc67x7S6CKA,299
53
+ api/user/model/requests.py,sha256=MazMirg7wQoUm4qvnm_EAB_gJIy3YxvYYqNyU3fZJc0,5025
54
+ api/user/model/responses.py,sha256=0vbigspq_aBd1JS6hEm13BnG7Hm7EIiWBZ6xz3d5hmE,202
30
55
  config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
56
  config/channels.json,sha256=GepystGi0h_1fuakC_gdIc-YYxyy-a4TI619ygIpyyM,156
32
57
  config/notification.json,sha256=esrOaMUIqfcCHB0Tawp3t4cu7DQAA15X12OS-Gyenb0,361
@@ -63,6 +88,24 @@ models/api.py,sha256=f5X56dudyEysPmDuI5grM2RRCXuIQoehaAB6wMAGG28,6473
63
88
  models/core.py,sha256=tEb7cbnC71yE9SDluynj7dE3U8Ed-EbF3uRJizy-uuU,16880
64
89
  models/enums.py,sha256=y2G5EKIc8FusVW4JvEozGFKL2GxjtuOK7k3zSguP4dc,5395
65
90
  plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
91
+ plugins/action_log/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
92
+ plugins/action_log/plugin.py,sha256=-JY_iIIJJjFFofvpMoCxNJMXNr_KC6kA8TiwvvoxaWI,4434
93
+ plugins/admin_notification_sender/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
94
+ plugins/admin_notification_sender/plugin.py,sha256=sxVGW8qtRmDEQeS6QD3F3IqzZOoh_9H_y2TsRCDkXaw,4771
95
+ plugins/ldap_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
96
+ plugins/ldap_manager/plugin.py,sha256=c05pKGsyLETMrheCqIw3gZOSLdTWvEDq_WCl0Q_0yXQ,3030
97
+ plugins/local_notification/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
98
+ plugins/local_notification/plugin.py,sha256=FObVxID5Bg0G_xStpJYZkg706wu_CpUqk09DmzHAyPQ,4074
99
+ plugins/realtime_updates_notifier/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
100
+ plugins/realtime_updates_notifier/plugin.py,sha256=Gcvob4ShSs2Ht1hLD2vtwhR_PSFYMv3_l_VPM2opTYs,2374
101
+ plugins/redis_db_update/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
102
+ plugins/redis_db_update/plugin.py,sha256=z05k1zNJgBnKPj-jrtMUeI9br75ZPlifbzL0HxpRnXg,7128
103
+ plugins/resource_folders_creation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
104
+ plugins/resource_folders_creation/plugin.py,sha256=OwYPtRjMt2esAAEdv1FjdZgjEz01yt2xOZQi3nB0kEQ,3327
105
+ plugins/system_notification_sender/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
106
+ plugins/system_notification_sender/plugin.py,sha256=96xzmuUGvO2WOJ5y90akDoRB7Bqy-VpOo6ht8-rLLtQ,8253
107
+ plugins/update_access_controls/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
108
+ plugins/update_access_controls/plugin.py,sha256=43UV4vg-zxBF_7Bv0AZH6gU0Bgy2ybapNK21wJTF05k,301
66
109
  pytests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
67
110
  pytests/api_user_models_erros_test.py,sha256=6VWLIhazYjz7avXIMIDpT6doiBO5FVzGsGJ3Cv8cXyg,583
68
111
  pytests/api_user_models_requests_test.py,sha256=1AYZcMwa-AVeGrhTgwIkwqw3w7_CDPkbaJ0YDxLLKdY,3859
@@ -99,8 +142,8 @@ utils/ticket_sys_utils.py,sha256=9QAlW2iiy8KyxQRBDj_WmzS5kKb0aYJmGwd4qzmGVqo,700
99
142
  utils/web_notifier.py,sha256=QM87VVid2grC5lK3NdS1yzz0z1wXljr4GChJOeK86W4,843
100
143
  utils/templates/activation.html.j2,sha256=XAMKCdoqONoc4ZQucD0yV-Pg5DlHHASZrTVItNS-iBE,640
101
144
  utils/templates/reminder.html.j2,sha256=aoS8bTs56q4hjAZKsb0jV9c-PIURBELuBOpT_qPZNVU,639
102
- dmart-0.1.6.dist-info/METADATA,sha256=ftqcSub4og9EUODZ17nuZMM0w5RIDwN8d3iPJh-zuqg,2091
103
- dmart-0.1.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
104
- dmart-0.1.6.dist-info/entry_points.txt,sha256=GjfoGh1bpxuU9HHGJzbtCFPNptHv9TryxHMN3uBSKpg,37
105
- dmart-0.1.6.dist-info/top_level.txt,sha256=JTypu1r5v9v7ru-60JSSbnSMEESHFRMacpcz-rPJx_U,262
106
- dmart-0.1.6.dist-info/RECORD,,
145
+ dmart-0.1.8.dist-info/METADATA,sha256=rm9HvMHuQkwfhoqO9mQfhMjHGA8_7DU__kyukUzXXwM,2091
146
+ dmart-0.1.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
147
+ dmart-0.1.8.dist-info/entry_points.txt,sha256=GjfoGh1bpxuU9HHGJzbtCFPNptHv9TryxHMN3uBSKpg,37
148
+ dmart-0.1.8.dist-info/top_level.txt,sha256=JTypu1r5v9v7ru-60JSSbnSMEESHFRMacpcz-rPJx_U,262
149
+ dmart-0.1.8.dist-info/RECORD,,
File without changes
@@ -0,0 +1,121 @@
1
+ import sys
2
+ from typing import Any
3
+ import aiofiles
4
+ from utils.middleware import get_request_data
5
+ from models.core import ActionType, PluginBase, Event, Payload
6
+ from models.enums import ContentType, ResourceType
7
+ from models.core import Action, Locator, Meta
8
+ from utils.helpers import camel_case
9
+ from utils.settings import settings
10
+ from datetime import datetime
11
+ from fastapi.logger import logger
12
+ from data_adapters.adapter import data_adapter as db
13
+
14
+
15
+ class Plugin(PluginBase):
16
+ async def hook(self, data: Event):
17
+ if settings.active_data_db == "sql":
18
+ return
19
+
20
+ if (
21
+ not isinstance(data.shortname, str)
22
+ or not isinstance(data.action_type, ActionType)
23
+ or not isinstance(data.resource_type, ResourceType)
24
+ or not isinstance(data.attributes, dict)
25
+ ):
26
+ logger.warning("invalid data at action_log")
27
+ return
28
+
29
+ class_type = getattr(
30
+ sys.modules["models.core"], camel_case(ResourceType(data.resource_type))
31
+ )
32
+
33
+ if data.action_type == ActionType.delete:
34
+ entry = data.attributes["entry"]
35
+ else:
36
+ entry = await db.load_or_none(
37
+ space_name=data.space_name,
38
+ subpath=data.subpath,
39
+ shortname=data.shortname,
40
+ class_type=class_type,
41
+ user_shortname=data.user_shortname,
42
+ )
43
+
44
+ if entry is None:
45
+ return
46
+
47
+ action_attributes = {}
48
+ if data.action_type == ActionType.create:
49
+ payload: dict[str,Any] = {}
50
+ if(
51
+ entry.payload and
52
+ isinstance(entry.payload, Payload) and
53
+ entry.payload.content_type and
54
+ entry.payload.content_type == ContentType.json
55
+ and entry.payload.body
56
+ ):
57
+ mypayload = await db.load_resource_payload(
58
+ space_name=data.space_name,
59
+ subpath=data.subpath,
60
+ filename=entry.payload.body if isinstance(entry.payload.body, str) else data.shortname,
61
+ class_type=class_type,
62
+ )
63
+ payload = mypayload if mypayload else {}
64
+ action_attributes = self.generate_create_event_attributes(entry, payload)
65
+
66
+ elif data.action_type == ActionType.update:
67
+ action_attributes = data.attributes.get("history_diff", {})
68
+
69
+ action_attributes = {**action_attributes, **get_request_data()}
70
+ action_attributes.pop("_sa_instance_state", None)
71
+ event_obj = Action(
72
+ resource=Locator(
73
+ uuid=entry.uuid,
74
+ type=data.resource_type,
75
+ space_name=data.space_name,
76
+ subpath=data.subpath,
77
+ shortname=data.shortname,
78
+ displayname=entry.displayname,
79
+ description=entry.description,
80
+ tags=entry.tags,
81
+ ),
82
+ user_shortname=data.user_shortname,
83
+ request=data.action_type,
84
+ timestamp=datetime.now(),
85
+ attributes=action_attributes,
86
+ )
87
+
88
+ events_file_path = (
89
+ settings.spaces_folder
90
+ / data.space_name
91
+ / ".dm"
92
+ )
93
+ events_file_path.mkdir(parents=True, exist_ok=True)
94
+ events_file_path = events_file_path / "events.jsonl"
95
+
96
+ # Remove binary content in the event object before serializing to json
97
+ if isinstance(event_obj.attributes, dict) and "media" in event_obj.attributes:
98
+ del event_obj.attributes["media"]
99
+
100
+ file_content = (
101
+ f"{event_obj.model_dump_json()}\n"
102
+ )
103
+ async with aiofiles.open(events_file_path, "a") as events_file:
104
+ await events_file.write(file_content)
105
+
106
+ def generate_create_event_attributes(self, entry: Meta, attributes: dict):
107
+ generated_attributes = {}
108
+ for key, value in entry.__dict__.items():
109
+ if key not in Meta.model_fields:
110
+ generated_attributes[key] = value
111
+
112
+ if entry.payload:
113
+ if isinstance(entry.payload, Payload):
114
+ generated_attributes["payload"] = entry.payload.model_dump()
115
+ else:
116
+ generated_attributes["payload"] = entry.payload
117
+
118
+ if attributes:
119
+ generated_attributes["payload"]["body"] = attributes
120
+
121
+ return generated_attributes
File without changes
@@ -0,0 +1,124 @@
1
+ import json
2
+ from sys import modules as sys_modules
3
+
4
+ from models import api
5
+ from models.core import Notification, NotificationData, PluginBase, Event, Translation
6
+ from utils.helpers import camel_case
7
+ from utils.notification import NotificationManager
8
+ from utils.settings import settings
9
+ from fastapi.logger import logger
10
+ from data_adapters.adapter import data_adapter as db
11
+
12
+ class Plugin(PluginBase):
13
+ async def hook(self, data: Event):
14
+ """
15
+ after creating a new admin notification request
16
+ 1- get the notification request
17
+ 2- if it's scheduled for later, ignore it and let the cron job handle it
18
+ 3- else send it to all its msisdns
19
+ """
20
+
21
+ # Type narrowing for PyRight
22
+ if not isinstance(data.shortname, str):
23
+ logger.warning(
24
+ "data.shortname is None and str is required at system_notification_sender"
25
+ )
26
+ return
27
+
28
+ notification_request_meta = await db.load(
29
+ data.space_name,
30
+ data.subpath,
31
+ data.shortname,
32
+ getattr(sys_modules["models.core"], camel_case(data.resource_type)),
33
+ data.user_shortname,
34
+ )
35
+
36
+ notification_dict = notification_request_meta.dict()
37
+ notification_dict["subpath"] = data.subpath
38
+ notification_request_payload = await db.get_payload_from_event(data)
39
+
40
+ notification_dict.update(notification_request_payload)
41
+
42
+ if not notification_dict or notification_dict.get("scheduled_at", False):
43
+ return
44
+
45
+ # Get msisdns users
46
+ search_criteria = notification_dict.get('search_string', '')
47
+ if not search_criteria:
48
+ search_criteria = '@msisdn:' + '|'.join(notification_dict.get('msisdns', ''))
49
+
50
+ total, receivers = await db.query(api.Query(
51
+ space_name=data.space_name,
52
+ subpath=notification_dict['subpath'],
53
+ filters={},
54
+ search=search_criteria,
55
+ limit=10000,
56
+ offset=0
57
+ ))
58
+ if total == 0:
59
+ return
60
+
61
+ sub_receivers: dict = receivers[0].model_dump()
62
+
63
+ receivers_shortnames = set()
64
+ for receiver in sub_receivers["data"]:
65
+ receivers_shortnames.add(json.loads(receiver)["shortname"])
66
+
67
+ # await send_notification(
68
+ # notification_dict=notification_dict,
69
+ # receivers=receivers_shortnames
70
+ # )
71
+ notification_manager = NotificationManager()
72
+ formatted_req = await self.prepare_request(notification_dict)
73
+ for receiver in set(receivers_shortnames):
74
+ if not formatted_req["push_only"]:
75
+ notification_obj = await Notification.from_request(notification_dict)
76
+ await db.internal_save_model(
77
+ "personal",
78
+ f"people/{receiver}/notifications",
79
+ notification_obj,
80
+ )
81
+ for platform in formatted_req["platforms"]:
82
+ await notification_manager.send(
83
+ platform=platform,
84
+ data=NotificationData(
85
+ receiver=receiver,
86
+ title=formatted_req["title"],
87
+ body=formatted_req["body"],
88
+ image_urls=formatted_req["images_urls"],
89
+ ),
90
+ )
91
+
92
+
93
+ notification_request_payload["status"] = "finished"
94
+ await db.save_payload_from_json(
95
+ space_name=data.space_name,
96
+ subpath=data.subpath,
97
+ meta=notification_request_meta,
98
+ payload_data=notification_request_payload,
99
+ )
100
+
101
+ async def prepare_request(self, notification_dict) -> dict:
102
+ # Get Notification Request Images
103
+ attachments_path = (
104
+ settings.spaces_folder
105
+ / f"{settings.management_space}/"
106
+ f"{notification_dict['subpath']}/.dm/{notification_dict['shortname']}"
107
+ )
108
+ notification_attachments = await db.get_entry_attachments(
109
+ subpath=f"{notification_dict['subpath']}/{notification_dict['shortname']}",
110
+ attachments_path=attachments_path,
111
+ )
112
+ notification_images = {
113
+ "en": notification_attachments.get("media", {}).get("en"),
114
+ "ar": notification_attachments.get("media", {}).get("ar"),
115
+ "ku": notification_attachments.get("media", {}).get("ku"),
116
+ }
117
+
118
+ return {
119
+ "platforms": notification_dict["types"],
120
+ "title": Translation(**notification_dict["displayname"]),
121
+ "body": Translation(**notification_dict["description"]),
122
+ "images_urls": Translation(**notification_images),
123
+ "push_only": notification_dict.get("push_only", False),
124
+ }
File without changes
@@ -0,0 +1,100 @@
1
+ from fastapi.logger import logger
2
+ from models.core import Event, PluginBase, User
3
+ from models.enums import ActionType
4
+ from utils.settings import settings
5
+ from data_adapters.adapter import data_adapter as db
6
+
7
+ from ldap3 import AUTO_BIND_NO_TLS, MODIFY_REPLACE, Server, Connection, ALL
8
+
9
+
10
+ class Plugin(PluginBase):
11
+
12
+ def __init__(self) -> None:
13
+ super().__init__()
14
+ try:
15
+ server = Server(settings.ldap_url, get_info=ALL)
16
+ self.conn = Connection(
17
+ server,
18
+ user=settings.ldap_admin_dn,
19
+ password=settings.ldap_pass,
20
+ auto_bind=AUTO_BIND_NO_TLS
21
+ )
22
+ except Exception:
23
+ logger.error(
24
+ "Failed to connect to LDAP"
25
+ )
26
+
27
+
28
+
29
+ async def hook(self, data: Event):
30
+ if not hasattr(self, "conn"):
31
+ return
32
+
33
+ # Type narrowing for PyRight
34
+ if not isinstance(data.shortname, str):
35
+ logger.warning(
36
+ "data.shortname is None and str is required at ldap_manager"
37
+ )
38
+ return
39
+
40
+ if data.action_type == ActionType.delete:
41
+ self.delete(data.shortname)
42
+ return
43
+
44
+ user_model: User = await db.load(
45
+ space_name=settings.management_space,
46
+ subpath=data.subpath,
47
+ shortname=data.shortname,
48
+ class_type=User
49
+ )
50
+
51
+ if data.action_type == ActionType.create:
52
+ self.add(data.shortname, user_model)
53
+
54
+ elif data.action_type == ActionType.update:
55
+ self.modify(data.shortname, user_model)
56
+
57
+
58
+ elif data.action_type == ActionType.move and "src_shortname" in data.attributes:
59
+ self.delete(data.attributes['src_shortname'])
60
+ self.add(data.shortname, user_model)
61
+
62
+
63
+
64
+ def delete(self, shortname: str):
65
+ self.conn.delete(f"cn={shortname},{settings.ldap_root_dn}")
66
+
67
+ def add(
68
+ self,
69
+ shortname: str,
70
+ user_model: User
71
+ ):
72
+ self.conn.add(
73
+ f"cn={shortname},{settings.ldap_root_dn}",
74
+ 'dmartUser',
75
+ {
76
+ "cn": shortname.encode(),
77
+ "sn": shortname.encode(),
78
+ "gn": str(getattr(user_model, "displayname", "")).encode(),
79
+ "userPassword": getattr(user_model, "password", "").encode()
80
+ }
81
+ )
82
+
83
+ def modify(
84
+ self,
85
+ shortname: str,
86
+ user_model: User
87
+ ):
88
+ self.conn.modify(
89
+ f"cn={shortname},{settings.ldap_root_dn}",
90
+ {
91
+ "gn": [(
92
+ MODIFY_REPLACE,
93
+ [str(getattr(user_model, "displayname", "")).encode()]
94
+ )],
95
+ "userPassword": [(
96
+ MODIFY_REPLACE,
97
+ [getattr(user_model, "password", "").encode()]
98
+ )],
99
+ }
100
+ )
File without changes