arpakitlib 1.8.19__py3-none-any.whl → 1.8.21__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 (57) hide show
  1. arpakitlib/_arpakit_project_template_v_5/example.env +1 -3
  2. arpakitlib/_arpakit_project_template_v_5/project/additional_model/common.py +0 -3
  3. arpakitlib/_arpakit_project_template_v_5/project/api/auth.py +170 -206
  4. arpakitlib/_arpakit_project_template_v_5/project/api/exception_handler.py +2 -7
  5. arpakitlib/_arpakit_project_template_v_5/project/api/router/admin/get_arpakitlib_project_template_info.py +11 -1
  6. arpakitlib/_arpakit_project_template_v_5/project/api/router/admin/get_auth_data.py +10 -8
  7. arpakitlib/_arpakit_project_template_v_5/project/api/router/admin/get_story_log.py +46 -0
  8. arpakitlib/_arpakit_project_template_v_5/project/api/router/admin/main_router.py +28 -0
  9. arpakitlib/_arpakit_project_template_v_5/project/api/router/admin/raise_fake_error.py +31 -0
  10. arpakitlib/_arpakit_project_template_v_5/project/api/router/admin/reinit_sqlalchemy_db.py +35 -0
  11. arpakitlib/_arpakit_project_template_v_5/project/api/router/client/get_current_user.py +33 -0
  12. arpakitlib/_arpakit_project_template_v_5/project/api/router/client/get_current_user_token.py +33 -0
  13. arpakitlib/_arpakit_project_template_v_5/project/api/router/client/get_errors_info.py +6 -8
  14. arpakitlib/_arpakit_project_template_v_5/project/api/router/client/main_router.py +17 -0
  15. arpakitlib/_arpakit_project_template_v_5/project/api/router/general/main_router.py +5 -1
  16. arpakitlib/_arpakit_project_template_v_5/project/api/router/{client → general}/now_utc_datetime.py +1 -1
  17. arpakitlib/_arpakit_project_template_v_5/project/api/schema/out/admin/common.py +7 -0
  18. arpakitlib/_arpakit_project_template_v_5/project/api/schema/out/admin/operation.py +2 -2
  19. arpakitlib/_arpakit_project_template_v_5/project/api/schema/out/admin/story_log.py +2 -2
  20. arpakitlib/_arpakit_project_template_v_5/project/api/schema/out/client/common.py +7 -0
  21. arpakitlib/_arpakit_project_template_v_5/project/api/schema/out/{admin → client}/user.py +6 -6
  22. arpakitlib/_arpakit_project_template_v_5/project/api/schema/out/client/user_token.py +14 -0
  23. arpakitlib/_arpakit_project_template_v_5/project/core/settings.py +3 -19
  24. arpakitlib/_arpakit_project_template_v_5/project/sqlalchemy_db_/sqlalchemy_model/api_key.py +1 -1
  25. arpakitlib/_arpakit_project_template_v_5/project/sqlalchemy_db_/sqlalchemy_model/operation.py +13 -3
  26. arpakitlib/_arpakit_project_template_v_5/project/sqlalchemy_db_/sqlalchemy_model/story_log.py +19 -4
  27. arpakitlib/_arpakit_project_template_v_5/project/sqlalchemy_db_/sqlalchemy_model/user.py +2 -2
  28. arpakitlib/_arpakit_project_template_v_5/project/sqlalchemy_db_/sqlalchemy_model/user_token.py +1 -1
  29. arpakitlib/_arpakit_project_template_v_5/project/test_data/make_test_api_keys.py +35 -0
  30. arpakitlib/_arpakit_project_template_v_5/project/test_data/make_test_data_1.py +4 -2
  31. arpakitlib/_arpakit_project_template_v_5/project/test_data/make_test_data_2.py +4 -2
  32. arpakitlib/_arpakit_project_template_v_5/project/test_data/make_test_data_3.py +4 -2
  33. arpakitlib/_arpakit_project_template_v_5/project/test_data/make_test_data_4.py +4 -2
  34. arpakitlib/_arpakit_project_template_v_5/project/test_data/make_test_data_5.py +4 -2
  35. arpakitlib/_arpakit_project_template_v_5/project/tg_bot/blank/client.py +14 -6
  36. arpakitlib/_arpakit_project_template_v_5/project/tg_bot/const.py +1 -0
  37. arpakitlib/_arpakit_project_template_v_5/project/tg_bot/router/{client → general}/about.py +1 -1
  38. arpakitlib/_arpakit_project_template_v_5/project/tg_bot/router/general/author.py +31 -0
  39. arpakitlib/_arpakit_project_template_v_5/project/tg_bot/router/{client → general}/support.py +1 -1
  40. arpakitlib/_arpakit_project_template_v_5/project/tg_bot/router/main_router.py +4 -3
  41. arpakitlib/_arpakit_project_template_v_5/project/tg_bot/util/set_tg_bot_commands.py +8 -0
  42. {arpakitlib-1.8.19.dist-info → arpakitlib-1.8.21.dist-info}/METADATA +1 -1
  43. {arpakitlib-1.8.19.dist-info → arpakitlib-1.8.21.dist-info}/RECORD +55 -49
  44. arpakitlib/_arpakit_project_template_v_5/project/api/auth2.py +0 -218
  45. arpakitlib/_arpakit_project_template_v_5/project/api/schema/out/admin/user_token.py +0 -14
  46. /arpakitlib/_arpakit_project_template_v_5/project/tg_bot/router/{client → general}/__init__.py +0 -0
  47. /arpakitlib/_arpakit_project_template_v_5/project/tg_bot/router/{client → general}/error_handler.py +0 -0
  48. /arpakitlib/_arpakit_project_template_v_5/project/tg_bot/router/{client → general}/healthcheck.py +0 -0
  49. /arpakitlib/_arpakit_project_template_v_5/project/tg_bot/router/{client → general}/hello_world.py +0 -0
  50. /arpakitlib/_arpakit_project_template_v_5/project/tg_bot/router/{client → general}/raw_callback_query.py +0 -0
  51. /arpakitlib/_arpakit_project_template_v_5/project/tg_bot/router/{client → general}/raw_inline_query.py +0 -0
  52. /arpakitlib/_arpakit_project_template_v_5/project/tg_bot/router/{client → general}/raw_message.py +0 -0
  53. /arpakitlib/_arpakit_project_template_v_5/project/tg_bot/router/{client → general}/remove_message.py +0 -0
  54. /arpakitlib/_arpakit_project_template_v_5/project/tg_bot/router/{client → general}/start.py +0 -0
  55. {arpakitlib-1.8.19.dist-info → arpakitlib-1.8.21.dist-info}/LICENSE +0 -0
  56. {arpakitlib-1.8.19.dist-info → arpakitlib-1.8.21.dist-info}/WHEEL +0 -0
  57. {arpakitlib-1.8.19.dist-info → arpakitlib-1.8.21.dist-info}/entry_points.txt +0 -0
@@ -11,12 +11,10 @@
11
11
  # api_port=
12
12
  # api_init_sqlalchemy_db=
13
13
  # api_init_json_db=
14
- # api_api_keys=
15
- # api_user_tokens=
14
+ # api_correct_api_keys=
16
15
  # api_enable_sqladmin=
17
16
  # api_start_operation_executor_worker=
18
17
  # api_start_scheduled_operation_creator_worker=
19
- # api_create_story_log_func_before_in_exception_handler=
20
18
  # sqladmin_secret_key=
21
19
  # sqladmin_auth_keys=
22
20
  # sqladmin_port=
@@ -1,8 +1,5 @@
1
- from typing import Any
2
-
3
1
  from pydantic import ConfigDict, BaseModel
4
2
 
5
3
 
6
4
  class BaseAM(BaseModel):
7
5
  model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True, from_attributes=True)
8
- additional_data: dict[str, Any] = {}
@@ -1,4 +1,4 @@
1
- from typing import Callable
1
+ from typing import Callable, Any
2
2
 
3
3
  import fastapi
4
4
  import fastapi.exceptions
@@ -8,10 +8,10 @@ import sqlalchemy
8
8
  from fastapi import Security
9
9
  from fastapi.security import APIKeyHeader
10
10
  from pydantic import BaseModel, ConfigDict
11
+ from sqlalchemy.orm import joinedload
11
12
 
12
13
  from arpakitlib.ar_func_util import is_async_func, is_sync_func
13
14
  from arpakitlib.ar_json_util import transfer_data_to_json_str_to_data
14
- from arpakitlib.ar_type_util import raise_for_type
15
15
  from project.api.const import APIErrorCodes
16
16
  from project.api.exception import APIException
17
17
  from project.core.settings import get_cached_settings
@@ -22,70 +22,27 @@ from project.sqlalchemy_db_.sqlalchemy_model import ApiKeyDBM, UserTokenDBM
22
22
  class APIAuthData(BaseModel):
23
23
  model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True, from_attributes=True)
24
24
 
25
- require_api_key_string: bool = False
26
- require_user_token_string: bool = False
27
-
28
- require_correct_api_key: bool = False
29
- require_correct_user_token: bool = False
30
-
31
- require_mode_type: str | None = None
32
- require_not_mode_type: str | None = None
33
-
34
- current_mode_type: str | None = None
35
-
36
25
  api_key_string: str | None = None
37
26
  user_token_string: str | None = None
38
27
 
39
28
  is_api_key_correct: bool | None = None
40
- is_user_token_correct: bool | None = None
41
29
 
42
30
  api_key_dbm: ApiKeyDBM | None = None
43
31
  user_token_dbm: UserTokenDBM | None = None
44
32
 
33
+ prod_mode: bool = False
45
34
 
46
- def api_auth(
47
- *,
48
- require_api_key_string: bool = False,
49
- require_user_token_string: bool = False,
50
-
51
- require_correct_api_key: bool = False,
52
- require_correct_user_token: bool = False,
35
+ extra_data: dict[str, Any] = {}
53
36
 
54
- require_mode_type: str | None = None,
55
- require_not_mode_type: str | None = None,
56
37
 
57
- is_api_key_correct_func: Callable | None = None,
58
- is_user_token_correct_func: Callable | None = None,
59
- correct_api_keys: str | list[str] | None = None,
60
- correct_user_tokens: str | list[str] | None = None,
38
+ def api_auth(
39
+ *,
40
+ middlewares: list[Callable] | None = None
61
41
  ) -> Callable:
62
- if isinstance(correct_api_keys, str):
63
- correct_api_keys = [correct_api_keys]
64
- if correct_api_keys is not None:
65
- raise_for_type(correct_api_keys, list)
66
-
67
- if is_api_key_correct_func is None and correct_api_keys is not None:
68
- is_api_key_correct_func = (
69
- lambda *args, **kwargs_: kwargs_["api_auth_data"].api_key_string in correct_api_keys
70
- )
71
-
72
- if isinstance(correct_user_tokens, str):
73
- correct_user_tokens = [correct_user_tokens]
74
- if correct_user_tokens is not None:
75
- raise_for_type(correct_user_tokens, list)
76
-
77
- if is_user_token_correct_func is None and correct_user_tokens is not None:
78
- is_user_token_correct_func = (
79
- lambda *args, **kwargs_: kwargs_["api_auth_data"].user_token_string in correct_user_tokens
80
- )
42
+ if middlewares is None:
43
+ middlewares = []
81
44
 
82
- if require_correct_api_key is True:
83
- require_api_key_string = True
84
-
85
- if require_correct_user_token is True:
86
- require_user_token_string = True
87
-
88
- async def func(
45
+ async def async_func(
89
46
  *,
90
47
  ac: fastapi.security.HTTPAuthorizationCredentials | None = fastapi.Security(
91
48
  fastapi.security.HTTPBearer(auto_error=False)
@@ -97,16 +54,10 @@ def api_auth(
97
54
  ) -> APIAuthData:
98
55
 
99
56
  api_auth_data = APIAuthData(
100
- require_api_key_string=require_api_key_string,
101
- require_user_token_string=require_user_token_string,
102
- require_correct_api_key=require_correct_api_key,
103
- require_correct_user_token=require_correct_user_token,
104
- require_mode_type=require_mode_type,
105
- require_not_mode_type=require_not_mode_type,
106
- current_mode_type=get_cached_settings().mode_type
57
+ prod_mode=get_cached_settings().prod_mode
107
58
  )
108
59
 
109
- # parse api_key
60
+ # parse api_key_string
110
61
 
111
62
  api_auth_data.api_key_string = api_key_string
112
63
 
@@ -116,6 +67,8 @@ def api_auth(
116
67
  api_auth_data.api_key_string = request.headers["api-key"]
117
68
  if not api_auth_data.api_key_string and "apikey" in request.headers.keys():
118
69
  api_auth_data.api_key_string = request.headers["apikey"]
70
+ if not api_auth_data.api_key_string and "api_key_string" in request.headers.keys():
71
+ api_auth_data.api_key_string = request.headers["api_key_string"]
119
72
 
120
73
  if not api_auth_data.api_key_string and "api_key" in request.query_params.keys():
121
74
  api_auth_data.api_key_string = request.query_params["api_key"]
@@ -123,13 +76,15 @@ def api_auth(
123
76
  api_auth_data.api_key_string = request.query_params["api-key"]
124
77
  if not api_auth_data.api_key_string and "apikey" in request.query_params.keys():
125
78
  api_auth_data.api_key_string = request.query_params["apikey"]
79
+ if not api_auth_data.api_key_string and "api_key_string" in request.query_params.keys():
80
+ api_auth_data.api_key_string = request.query_params["api_key_string"]
126
81
 
127
82
  if api_auth_data.api_key_string:
128
83
  api_auth_data.api_key_string = api_auth_data.api_key_string.strip()
129
84
  if not api_auth_data.api_key_string:
130
85
  api_auth_data.api_key_string = None
131
86
 
132
- # parse user token
87
+ # parse user_token_string
133
88
 
134
89
  api_auth_data.user_token_string = ac.credentials if ac and ac.credentials and ac.credentials.strip() else None
135
90
 
@@ -158,196 +113,205 @@ def api_auth(
158
113
  if not api_auth_data.user_token_string:
159
114
  api_auth_data.user_token_string = None
160
115
 
161
- # require_mode_type
116
+ # is_api_key_correct
162
117
 
163
- if require_mode_type is not None:
164
- if get_cached_settings().mode_type != require_mode_type:
165
- raise APIException(
166
- status_code=fastapi.status.HTTP_401_UNAUTHORIZED,
167
- error_code=APIErrorCodes.cannot_authorize,
168
- error_data=transfer_data_to_json_str_to_data(api_auth_data.model_dump())
118
+ if api_auth_data.api_key_string is not None:
119
+ if get_cached_settings().api_correct_api_keys is None:
120
+ api_auth_data.is_api_key_correct = None
121
+ else:
122
+ api_auth_data.is_api_key_correct = (
123
+ api_auth_data.api_key_string in get_cached_settings().api_correct_api_keys
169
124
  )
170
125
 
171
- # require_not_mode_type
172
-
173
- if require_not_mode_type is not None:
174
- if get_cached_settings().mode_type == require_not_mode_type:
175
- raise APIException(
176
- status_code=fastapi.status.HTTP_401_UNAUTHORIZED,
177
- error_code=APIErrorCodes.cannot_authorize,
178
- error_data=transfer_data_to_json_str_to_data(api_auth_data.model_dump())
179
- )
126
+ # api_key_dbm
180
127
 
181
- # require_api_key_string
128
+ if api_auth_data.api_key_string is not None:
129
+ if get_cached_sqlalchemy_db() is not None:
130
+ async with get_cached_sqlalchemy_db().new_async_session() as async_session:
131
+ api_auth_data.api_key_dbm = await async_session.scalar(
132
+ sqlalchemy.select(ApiKeyDBM).where(ApiKeyDBM.value == api_auth_data.api_key_string)
133
+ )
182
134
 
183
- if require_api_key_string and not api_auth_data.api_key_string:
184
- raise APIException(
185
- status_code=fastapi.status.HTTP_401_UNAUTHORIZED,
186
- error_code=APIErrorCodes.cannot_authorize,
187
- error_data=transfer_data_to_json_str_to_data(api_auth_data.model_dump())
188
- )
135
+ # user_token_dbm
189
136
 
190
- # require_token_string
137
+ if api_auth_data.user_token_string is not None:
138
+ if get_cached_sqlalchemy_db() is not None:
139
+ async with get_cached_sqlalchemy_db().new_async_session() as async_session:
140
+ api_auth_data.user_token_dbm = await async_session.scalar(
141
+ sqlalchemy.select(UserTokenDBM)
142
+ .options(joinedload(UserTokenDBM.user))
143
+ .where(UserTokenDBM.value == api_auth_data.user_token_string)
144
+ )
191
145
 
192
- if require_user_token_string and not api_auth_data.user_token_string:
193
- raise APIException(
194
- status_code=fastapi.status.HTTP_401_UNAUTHORIZED,
195
- error_code=APIErrorCodes.cannot_authorize,
196
- error_data=transfer_data_to_json_str_to_data(api_auth_data.model_dump())
197
- )
146
+ # middlewares
198
147
 
199
- # is_api_key_correct_func
148
+ for middleware in middlewares:
149
+ api_auth_data.extra_data[middleware.__name__] = True
200
150
 
201
- if is_api_key_correct_func is not None:
202
- if is_async_func(is_api_key_correct_func):
203
- await is_api_key_correct_func(
151
+ for middleware in middlewares:
152
+ if is_async_func(middleware):
153
+ await middleware(
204
154
  api_auth_data=api_auth_data,
205
155
  request=request
206
156
  )
207
- elif is_sync_func(is_api_key_correct_func):
208
- is_api_key_correct_func(
157
+ elif is_sync_func(middleware):
158
+ middleware(
209
159
  api_auth_data=api_auth_data,
210
160
  request=request
211
161
  )
212
162
  else:
213
- raise TypeError("unknown validate_api_key_func type")
163
+ raise TypeError(f"unknown middleware type, {middleware.__name__}")
214
164
 
215
- # is_user_token_correct_func
165
+ return api_auth_data
216
166
 
217
- if is_user_token_correct_func is not None:
218
- if is_async_func(is_user_token_correct_func):
219
- await is_user_token_correct_func(
220
- api_auth_data=api_auth_data,
221
- request=request
222
- )
223
- elif is_sync_func(is_user_token_correct_func):
224
- is_user_token_correct_func(
225
- api_auth_data=api_auth_data,
226
- request=request
227
- )
228
- else:
229
- raise TypeError("unknown validate_token_func type")
167
+ return async_func
230
168
 
231
- # require_correct_api_key
232
169
 
233
- if require_correct_api_key:
234
- if not api_auth_data.is_api_key_correct:
235
- raise APIException(
236
- status_code=fastapi.status.HTTP_401_UNAUTHORIZED,
237
- error_code=APIErrorCodes.cannot_authorize,
238
- error_description="not api_auth_data.is_api_key_correct",
239
- error_data=transfer_data_to_json_str_to_data(api_auth_data.model_dump()),
240
- )
170
+ def require_prod_mode_api_middleware():
171
+ def func(*, api_auth_data: APIAuthData, request: fastapi.requests.Request):
172
+ if not get_cached_settings().prod_mode:
173
+ raise APIException(
174
+ status_code=fastapi.status.HTTP_401_UNAUTHORIZED,
175
+ error_code=APIErrorCodes.cannot_authorize,
176
+ error_description=f"prod_mode is required, {get_cached_settings().prod_mode=}",
177
+ error_data=transfer_data_to_json_str_to_data(api_auth_data.model_dump())
178
+ )
241
179
 
242
- # require_correct_token
180
+ func.__name__ = require_prod_mode_api_middleware.__name__
243
181
 
244
- if require_correct_user_token:
245
- if not api_auth_data.is_user_token_correct:
246
- raise APIException(
247
- status_code=fastapi.status.HTTP_401_UNAUTHORIZED,
248
- error_code=APIErrorCodes.cannot_authorize,
249
- error_description="not api_auth_data.is_user_token_correct",
250
- error_data=transfer_data_to_json_str_to_data(api_auth_data.model_dump())
251
- )
182
+ return func
252
183
 
253
- return api_auth_data
184
+
185
+ def require_not_prod_mode_api_middleware():
186
+ def func(*, api_auth_data: APIAuthData, request: fastapi.requests.Request):
187
+ if get_cached_settings().prod_mode:
188
+ raise APIException(
189
+ status_code=fastapi.status.HTTP_401_UNAUTHORIZED,
190
+ error_code=APIErrorCodes.cannot_authorize,
191
+ error_description=f"not prod_mode is required, {get_cached_settings().prod_mode=}",
192
+ error_data=transfer_data_to_json_str_to_data(api_auth_data.model_dump())
193
+ )
194
+
195
+ func.__name__ = require_not_prod_mode_api_middleware.__name__
254
196
 
255
197
  return func
256
198
 
257
199
 
258
- def correct_api_keys_from_settings__is_api_key_correct_func() -> Callable:
259
- async def async_func(
260
- *,
261
- api_auth_data: APIAuthData,
262
- request: fastapi.requests.Request,
263
- ):
264
- if get_cached_settings().api_correct_api_keys is None:
265
- api_auth_data.is_api_key_correct = False
266
- return
200
+ def require_api_key_string_api_middleware():
201
+ def func(*, api_auth_data: APIAuthData, request: fastapi.requests.Request):
267
202
  if api_auth_data.api_key_string is None:
268
- api_auth_data.is_api_key_correct = False
269
- return
270
- if api_auth_data.api_key_string.strip() not in get_cached_settings().api_correct_api_keys:
271
- api_auth_data.is_api_key_correct = False
272
- return
273
- api_auth_data.is_api_key_correct = True
274
- return
203
+ raise APIException(
204
+ status_code=fastapi.status.HTTP_401_UNAUTHORIZED,
205
+ error_code=APIErrorCodes.cannot_authorize,
206
+ error_description="api_key_string is required",
207
+ error_data=transfer_data_to_json_str_to_data(api_auth_data.model_dump())
208
+ )
275
209
 
276
- return async_func
210
+ func.__name__ = require_api_key_string_api_middleware.__name__
277
211
 
212
+ return func
278
213
 
279
- def correct_tokens_from_settings__is_user_token_correct_func() -> Callable:
280
- async def async_func(
281
- *,
282
- api_auth_data: APIAuthData,
283
- request: fastapi.requests.Request,
284
- ):
285
- if get_cached_settings().api_correct_tokens is None:
286
- api_auth_data.is_api_key_correct = False
287
- return
214
+
215
+ def require_user_token_string_api_middleware():
216
+ def func(*, api_auth_data: APIAuthData, request: fastapi.requests.Request):
288
217
  if api_auth_data.user_token_string is None:
289
- api_auth_data.is_api_key_correct = False
290
- return
291
- if api_auth_data.user_token_string.strip() not in get_cached_settings().api_correct_tokens:
292
- api_auth_data.is_api_key_correct = False
293
- return
294
- api_auth_data.is_api_key_correct = True
295
- return
218
+ raise APIException(
219
+ status_code=fastapi.status.HTTP_401_UNAUTHORIZED,
220
+ error_code=APIErrorCodes.cannot_authorize,
221
+ error_description="user_token_string is required",
222
+ error_data=transfer_data_to_json_str_to_data(api_auth_data.model_dump())
223
+ )
296
224
 
297
- return async_func
225
+ func.__name__ = require_user_token_string_api_middleware.__name__
298
226
 
227
+ return func
299
228
 
300
- def correct_api_key_from_sqlalchemy_db__is_api_key_correct_func() -> Callable:
301
- async def async_func(
302
- *,
303
- api_auth_data: APIAuthData,
304
- request: fastapi.requests.Request,
305
- ):
306
- if api_auth_data.api_key_string is None:
307
- api_auth_data.is_api_key_correct = False
308
- return
309
229
 
310
- async with get_cached_sqlalchemy_db().new_async_session() as session:
311
- api_auth_data.api_key_dbm = await session.scalar(
312
- sqlalchemy.select(ApiKeyDBM).where(ApiKeyDBM.value == api_auth_data.api_key_string)
230
+ def require_correct_api_key_api_middleware():
231
+ def func(*, api_auth_data: APIAuthData, request: fastapi.requests.Request):
232
+ if not api_auth_data.is_api_key_correct:
233
+ raise APIException(
234
+ status_code=fastapi.status.HTTP_401_UNAUTHORIZED,
235
+ error_code=APIErrorCodes.cannot_authorize,
236
+ error_description="correct api_key_string is required",
237
+ error_data=transfer_data_to_json_str_to_data(api_auth_data.model_dump())
313
238
  )
314
239
 
315
- if api_auth_data.api_key_dbm is None or not api_auth_data.api_key_dbm.is_enabled:
316
- api_auth_data.is_api_key_correct = False
317
- return
240
+ func.__name__ = require_correct_api_key_api_middleware.__name__
241
+
242
+ return func
318
243
 
319
- api_auth_data.is_api_key_correct = True
320
- return True
244
+
245
+ def require_api_key_dbm_api_middleware(*, require_active: bool = True):
246
+ async def async_func(*, api_auth_data: APIAuthData, request: fastapi.requests.Request):
247
+ if api_auth_data.api_key_dbm is None:
248
+ raise APIException(
249
+ status_code=fastapi.status.HTTP_401_UNAUTHORIZED,
250
+ error_code=APIErrorCodes.cannot_authorize,
251
+ error_description=f"api_key_dbm is required, {require_active=}",
252
+ error_data=transfer_data_to_json_str_to_data(api_auth_data.model_dump())
253
+ )
254
+ if require_active and not api_auth_data.api_key_dbm.is_active:
255
+ raise APIException(
256
+ status_code=fastapi.status.HTTP_401_UNAUTHORIZED,
257
+ error_code=APIErrorCodes.cannot_authorize,
258
+ error_description=f"api_key_dbm is required, {require_active=}",
259
+ error_data=transfer_data_to_json_str_to_data(api_auth_data.model_dump())
260
+ )
261
+
262
+ async_func.__name__ = require_api_key_dbm_api_middleware.__name__
321
263
 
322
264
  return async_func
323
265
 
324
266
 
325
- def correct_user_token_from_sqlalchemy_db__is_user_token_correct_func(
326
- *, require_user_roles: list[str] | None = None
327
- ) -> Callable:
328
- async def async_func(
329
- *,
330
- api_auth_data: APIAuthData,
331
- request: fastapi.requests.Request,
332
- ):
333
- if api_auth_data.user_token_string is None:
334
- api_auth_data.is_user_token_correct = False
335
- return
267
+ def require_correct_api_key_or_api_key_dbm_api_middleware(*, require_active_api_key_dbm: bool = True):
268
+ async def async_func(*, api_auth_data: APIAuthData, request: fastapi.requests.Request):
269
+ if not api_auth_data.is_api_key_correct and (
270
+ api_auth_data.api_key_dbm is None
271
+ or (require_active_api_key_dbm and not api_auth_data.api_key_dbm.is_active)
272
+ ):
273
+ raise APIException(
274
+ status_code=fastapi.status.HTTP_401_UNAUTHORIZED,
275
+ error_code=APIErrorCodes.cannot_authorize,
276
+ error_description=(
277
+ f"correct api_key is required or api_key_dbm is required, {require_active_api_key_dbm=}"
278
+ ),
279
+ error_data=transfer_data_to_json_str_to_data(api_auth_data.model_dump())
280
+ )
281
+
282
+ async_func.__name__ = require_correct_api_key_or_api_key_dbm_api_middleware.__name__
283
+
284
+ return async_func
336
285
 
337
- with get_cached_sqlalchemy_db().new_session() as session:
338
- api_auth_data.user_token_dbm = session.query(
339
- UserTokenDBM
340
- ).filter(
341
- UserTokenDBM.value == api_auth_data.user_token_string
342
- ).one_or_none()
343
286
 
287
+ def require_user_token_dbm_api_middleware(
288
+ *, require_active_user_token: bool = True,
289
+ require_user_roles: list[str] | None = None
290
+ ):
291
+ async def async_func(*, api_auth_data: APIAuthData, request: fastapi.requests.Request):
344
292
  if api_auth_data.user_token_dbm is None:
345
- api_auth_data.is_user_token_correct = False
346
- return
347
- if not api_auth_data.user_token_dbm.is_enabled:
348
- pass
293
+ raise APIException(
294
+ status_code=fastapi.status.HTTP_401_UNAUTHORIZED,
295
+ error_code=APIErrorCodes.cannot_authorize,
296
+ error_description=f"user_key_dbm is required, {require_active_user_token=}, {require_user_roles=}",
297
+ error_data=transfer_data_to_json_str_to_data(api_auth_data.model_dump())
298
+ )
299
+ if require_active_user_token and not api_auth_data.user_token_dbm.is_active:
300
+ raise APIException(
301
+ status_code=fastapi.status.HTTP_401_UNAUTHORIZED,
302
+ error_code=APIErrorCodes.cannot_authorize,
303
+ error_description=f"user_key_dbm is required, {require_active_user_token=}, {require_user_roles=}",
304
+ error_data=transfer_data_to_json_str_to_data(api_auth_data.model_dump())
305
+ )
306
+ if require_user_roles is not None:
307
+ if not api_auth_data.user_token_dbm.user.compare_roles(require_user_roles):
308
+ raise APIException(
309
+ status_code=fastapi.status.HTTP_403_FORBIDDEN,
310
+ error_code=APIErrorCodes.cannot_authorize,
311
+ error_description=f"user_key_dbm is required, {require_active_user_token=}, {require_user_roles=}",
312
+ error_data=transfer_data_to_json_str_to_data(api_auth_data.model_dump())
313
+ )
349
314
 
350
- api_auth_data.is_user_token_correct = True
351
- return
315
+ async_func.__name__ = require_user_token_dbm_api_middleware.__name__
352
316
 
353
317
  return async_func
@@ -11,12 +11,10 @@ from arpakitlib.ar_datetime_util import now_utc_dt
11
11
  from arpakitlib.ar_dict_util import combine_dicts
12
12
  from arpakitlib.ar_exception_util import exception_to_traceback_str
13
13
  from arpakitlib.ar_func_util import raise_if_not_async_func, is_async_func, is_sync_func
14
- from arpakitlib.ar_json_util import transfer_data_to_json_str
15
14
  from project.api.const import APIErrorCodes
16
15
  from project.api.exception import APIException
17
16
  from project.api.response import APIJSONResponse
18
17
  from project.api.schema.out.common.error import ErrorCommonSO
19
- from project.core.settings import get_cached_settings
20
18
  from project.sqlalchemy_db_.sqlalchemy_db import get_cached_sqlalchemy_db
21
19
  from project.sqlalchemy_db_.sqlalchemy_model import StoryLogDBM
22
20
 
@@ -175,7 +173,7 @@ def logging_func_before_in_api_exception_handler(
175
173
  ):
176
174
  return
177
175
 
178
- _logger.error(transfer_data_to_json_str(error_common_so.model_dump()), exc_info=False)
176
+ _logger.exception(exception)
179
177
 
180
178
  return func
181
179
 
@@ -237,10 +235,7 @@ def get_exception_handler() -> Callable:
237
235
  funcs_before = []
238
236
  async_funcs_after = []
239
237
 
240
- if (
241
- get_cached_settings().api_create_story_log_func_before_in_api_exception_handler
242
- and get_cached_sqlalchemy_db() is not None
243
- ):
238
+ if get_cached_sqlalchemy_db() is not None:
244
239
  funcs_before.append(
245
240
  create_story_log_func_before_in_api_exception_handler(
246
241
  ignore_api_error_codes=[
@@ -1,8 +1,11 @@
1
1
  import fastapi
2
2
  from fastapi import APIRouter
3
3
 
4
+ from project.api.auth import require_user_token_dbm_api_middleware, APIAuthData, \
5
+ api_auth, require_correct_api_key_or_api_key_dbm_api_middleware
4
6
  from project.api.schema.out.common.error import ErrorCommonSO
5
7
  from project.api.schema.out.common.raw_data import RawDataCommonSO
8
+ from project.sqlalchemy_db_.sqlalchemy_model import UserDBM
6
9
  from project.util.arpakitlib_project_template import get_arpakitlib_project_template_info
7
10
 
8
11
  api_router = APIRouter()
@@ -17,7 +20,14 @@ api_router = APIRouter()
17
20
  async def _(
18
21
  *,
19
22
  request: fastapi.requests.Request,
20
- response: fastapi.responses.Response
23
+ response: fastapi.responses.Response,
24
+ api_auth_data: APIAuthData = fastapi.Depends(api_auth(middlewares=[
25
+ require_correct_api_key_or_api_key_dbm_api_middleware(require_active_api_key_dbm=True),
26
+ require_user_token_dbm_api_middleware(
27
+ require_active_user_token=True,
28
+ require_user_roles=[UserDBM.Roles.admin]
29
+ )
30
+ ]))
21
31
  ):
22
32
  arpakitlib_project_template_data = get_arpakitlib_project_template_info()
23
33
  return RawDataCommonSO(data=arpakitlib_project_template_data)
@@ -2,9 +2,11 @@ import fastapi.requests
2
2
  from fastapi import APIRouter
3
3
 
4
4
  from arpakitlib.ar_json_util import transfer_data_to_json_str_to_data
5
- from project.api.auth import APIAuthData, api_auth
5
+ from project.api.auth import APIAuthData, api_auth, require_user_token_dbm_api_middleware, \
6
+ require_correct_api_key_or_api_key_dbm_api_middleware
6
7
  from project.api.schema.out.common.error import ErrorCommonSO
7
8
  from project.api.schema.out.common.raw_data import RawDataCommonSO
9
+ from project.sqlalchemy_db_.sqlalchemy_model import UserDBM
8
10
 
9
11
  api_router = APIRouter()
10
12
 
@@ -19,12 +21,12 @@ async def _(
19
21
  *,
20
22
  request: fastapi.requests.Request,
21
23
  response: fastapi.responses.Response,
22
- api_auth_data: APIAuthData = fastapi.Depends(api_auth(
23
- require_not_prod_mode=True,
24
- try_find_api_key_dbm_from_sqlalchemy_db=True,
25
- try_find_user_token_dbm_from_sqlalchemy_db=True,
26
- require_api_key_dbm_from_sqlalchemy_db=True,
27
- require_user_token_dbm_from_sqlalchemy_db=True
28
- ))
24
+ api_auth_data: APIAuthData = fastapi.Depends(api_auth(middlewares=[
25
+ require_correct_api_key_or_api_key_dbm_api_middleware(require_active_api_key_dbm=True),
26
+ require_user_token_dbm_api_middleware(
27
+ require_active_user_token=True,
28
+ require_user_roles=[UserDBM.Roles.admin]
29
+ )
30
+ ]))
29
31
  ):
30
32
  return RawDataCommonSO(data=transfer_data_to_json_str_to_data(api_auth_data.model_dump()))
@@ -0,0 +1,46 @@
1
+ import fastapi.requests
2
+ import sqlalchemy
3
+ from fastapi import APIRouter
4
+
5
+ from project.api.auth import APIAuthData, api_auth, require_user_token_dbm_api_middleware, \
6
+ require_correct_api_key_or_api_key_dbm_api_middleware
7
+ from project.api.schema.out.admin.story_log import StoryLogAdminSO
8
+ from project.api.schema.out.common.error import ErrorCommonSO
9
+ from project.sqlalchemy_db_.sqlalchemy_db import get_cached_sqlalchemy_db
10
+ from project.sqlalchemy_db_.sqlalchemy_model import UserDBM, StoryLogDBM
11
+
12
+ api_router = APIRouter()
13
+
14
+
15
+ @api_router.get(
16
+ "",
17
+ name="Get story log",
18
+ status_code=fastapi.status.HTTP_200_OK,
19
+ response_model=StoryLogAdminSO | None | ErrorCommonSO,
20
+ )
21
+ async def _(
22
+ *,
23
+ request: fastapi.requests.Request,
24
+ response: fastapi.responses.Response,
25
+ api_auth_data: APIAuthData = fastapi.Depends(api_auth(middlewares=[
26
+ require_correct_api_key_or_api_key_dbm_api_middleware(require_active_api_key_dbm=True),
27
+ require_user_token_dbm_api_middleware(
28
+ require_active_user_token=True,
29
+ require_user_roles=[UserDBM.Roles.admin]
30
+ )
31
+ ])),
32
+ filter_id: int | None = fastapi.Query(default=None),
33
+ filter_long_id: str | None = fastapi.Query(default=None),
34
+ ):
35
+ if filter_id is None and filter_long_id is None:
36
+ return None
37
+
38
+ query = sqlalchemy.select(StoryLogDBM)
39
+ if filter_id is not None:
40
+ query = query.filter(StoryLogDBM.id == filter_id)
41
+ if filter_long_id is not None:
42
+ query = query.filter(StoryLogDBM.long_id == filter_long_id)
43
+
44
+ async with get_cached_sqlalchemy_db().new_async_session() as async_session:
45
+ result = await async_session.scalar(query)
46
+ return StoryLogAdminSO.from_dbm(simple_dbm=result)