fastapi-rtk 0.2.27__py3-none-any.whl → 1.0.13__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.
- fastapi_rtk/__init__.py +39 -35
- fastapi_rtk/_version.py +1 -0
- fastapi_rtk/api/model_rest_api.py +476 -221
- fastapi_rtk/auth/auth.py +0 -9
- fastapi_rtk/backends/generic/__init__.py +6 -0
- fastapi_rtk/backends/generic/column.py +21 -12
- fastapi_rtk/backends/generic/db.py +42 -7
- fastapi_rtk/backends/generic/filters.py +21 -16
- fastapi_rtk/backends/generic/interface.py +14 -8
- fastapi_rtk/backends/generic/model.py +19 -11
- fastapi_rtk/backends/sqla/__init__.py +1 -0
- fastapi_rtk/backends/sqla/db.py +77 -17
- fastapi_rtk/backends/sqla/extensions/audit/audit.py +401 -189
- fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py +15 -12
- fastapi_rtk/backends/sqla/filters.py +50 -21
- fastapi_rtk/backends/sqla/interface.py +96 -34
- fastapi_rtk/backends/sqla/model.py +56 -39
- fastapi_rtk/bases/__init__.py +20 -0
- fastapi_rtk/bases/db.py +94 -7
- fastapi_rtk/bases/file_manager.py +47 -3
- fastapi_rtk/bases/filter.py +22 -0
- fastapi_rtk/bases/interface.py +49 -5
- fastapi_rtk/bases/model.py +3 -0
- fastapi_rtk/bases/session.py +2 -0
- fastapi_rtk/cli/cli.py +62 -9
- fastapi_rtk/cli/commands/__init__.py +23 -0
- fastapi_rtk/cli/{db.py → commands/db/__init__.py} +107 -50
- fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/env.py +2 -3
- fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/env.py +10 -9
- fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/script.py.mako +3 -1
- fastapi_rtk/cli/{export.py → commands/export.py} +12 -10
- fastapi_rtk/cli/{security.py → commands/security.py} +73 -7
- fastapi_rtk/cli/commands/translate.py +299 -0
- fastapi_rtk/cli/decorators.py +9 -4
- fastapi_rtk/cli/utils.py +46 -0
- fastapi_rtk/config.py +41 -1
- fastapi_rtk/const.py +29 -1
- fastapi_rtk/db.py +76 -40
- fastapi_rtk/decorators.py +1 -1
- fastapi_rtk/dependencies.py +134 -62
- fastapi_rtk/exceptions.py +51 -1
- fastapi_rtk/fastapi_react_toolkit.py +186 -171
- fastapi_rtk/file_managers/file_manager.py +8 -6
- fastapi_rtk/file_managers/s3_file_manager.py +69 -33
- fastapi_rtk/globals.py +22 -12
- fastapi_rtk/lang/__init__.py +3 -0
- fastapi_rtk/lang/babel/__init__.py +4 -0
- fastapi_rtk/lang/babel/cli.py +40 -0
- fastapi_rtk/lang/babel/config.py +17 -0
- fastapi_rtk/lang/babel.cfg +1 -0
- fastapi_rtk/lang/lazy_text.py +120 -0
- fastapi_rtk/lang/messages.pot +238 -0
- fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.mo +0 -0
- fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.po +248 -0
- fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.mo +0 -0
- fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.po +244 -0
- fastapi_rtk/manager.py +355 -37
- fastapi_rtk/mixins.py +12 -0
- fastapi_rtk/routers.py +208 -72
- fastapi_rtk/schemas.py +142 -39
- fastapi_rtk/security/sqla/apis.py +39 -13
- fastapi_rtk/security/sqla/models.py +8 -23
- fastapi_rtk/security/sqla/security_manager.py +369 -11
- fastapi_rtk/setting.py +446 -88
- fastapi_rtk/types.py +94 -27
- fastapi_rtk/utils/__init__.py +8 -0
- fastapi_rtk/utils/async_task_runner.py +286 -61
- fastapi_rtk/utils/csv_json_converter.py +243 -40
- fastapi_rtk/utils/hooks.py +34 -0
- fastapi_rtk/utils/merge_schema.py +3 -3
- fastapi_rtk/utils/multiple_async_contexts.py +21 -0
- fastapi_rtk/utils/pydantic.py +46 -1
- fastapi_rtk/utils/run_utils.py +31 -1
- fastapi_rtk/utils/self_dependencies.py +1 -1
- fastapi_rtk/utils/use_default_when_none.py +1 -1
- fastapi_rtk/version.py +6 -1
- fastapi_rtk-1.0.13.dist-info/METADATA +28 -0
- fastapi_rtk-1.0.13.dist-info/RECORD +133 -0
- {fastapi_rtk-0.2.27.dist-info → fastapi_rtk-1.0.13.dist-info}/WHEEL +1 -2
- fastapi_rtk/backends/gremlinpython/__init__.py +0 -108
- fastapi_rtk/backends/gremlinpython/column.py +0 -208
- fastapi_rtk/backends/gremlinpython/db.py +0 -228
- fastapi_rtk/backends/gremlinpython/exceptions.py +0 -34
- fastapi_rtk/backends/gremlinpython/filters.py +0 -461
- fastapi_rtk/backends/gremlinpython/interface.py +0 -734
- fastapi_rtk/backends/gremlinpython/model.py +0 -364
- fastapi_rtk/backends/gremlinpython/session.py +0 -23
- fastapi_rtk/cli/commands.py +0 -295
- fastapi_rtk-0.2.27.dist-info/METADATA +0 -23
- fastapi_rtk-0.2.27.dist-info/RECORD +0 -126
- fastapi_rtk-0.2.27.dist-info/top_level.txt +0 -1
- /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/README +0 -0
- /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/alembic.ini.mako +0 -0
- /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/script.py.mako +0 -0
- /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/README +0 -0
- /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/alembic.ini.mako +0 -0
- {fastapi_rtk-0.2.27.dist-info → fastapi_rtk-1.0.13.dist-info}/entry_points.txt +0 -0
- {fastapi_rtk-0.2.27.dist-info → fastapi_rtk-1.0.13.dist-info}/licenses/LICENSE +0 -0
fastapi_rtk/schemas.py
CHANGED
|
@@ -3,7 +3,7 @@ import typing
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from typing import Annotated, Any, Dict, Literal
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import fastapi
|
|
7
7
|
from pydantic import (
|
|
8
8
|
AfterValidator,
|
|
9
9
|
BaseModel,
|
|
@@ -16,6 +16,8 @@ from pydantic import (
|
|
|
16
16
|
)
|
|
17
17
|
|
|
18
18
|
from .const import logger
|
|
19
|
+
from .exceptions import HTTPWithValidationException
|
|
20
|
+
from .lang import translate
|
|
19
21
|
from .utils import ensure_tz_info, merge_schema, validate_utc
|
|
20
22
|
|
|
21
23
|
if typing.TYPE_CHECKING:
|
|
@@ -272,11 +274,13 @@ class InfoResponse(BaseModel):
|
|
|
272
274
|
add_title: str = ""
|
|
273
275
|
add_schema: dict[str, Any] = {}
|
|
274
276
|
add_uischema: dict[str, Any] | None = None
|
|
277
|
+
add_translations: dict[str, dict[str, str]] | None = None
|
|
275
278
|
add_type: typing.Literal["json"] | typing.Literal["form"] = "json"
|
|
276
279
|
edit_columns: list[ColumnInfo | ColumnEnumInfo | ColumnRelationInfo] = []
|
|
277
280
|
edit_title: str = ""
|
|
278
281
|
edit_schema: dict[str, Any] = {}
|
|
279
282
|
edit_uischema: dict[str, Any] | None = None
|
|
283
|
+
edit_translations: dict[str, dict[str, str]] | None = None
|
|
280
284
|
edit_type: typing.Literal["json"] | typing.Literal["form"] = "json"
|
|
281
285
|
filters: dict[str, Any] = {}
|
|
282
286
|
filter_options: dict[str, Any] = {}
|
|
@@ -319,7 +323,7 @@ class BaseResponseMany(BaseResponse):
|
|
|
319
323
|
Represents a response containing multiple items.
|
|
320
324
|
|
|
321
325
|
Attributes:
|
|
322
|
-
count (int): The count of items.
|
|
326
|
+
count (int | None): The count of items. If None, count is not provided.
|
|
323
327
|
ids (List[PRIMARY_KEY]): The IDs of the items.
|
|
324
328
|
list_columns (List[str]): The columns to show in the list.
|
|
325
329
|
list_title (str): The title for the list.
|
|
@@ -327,7 +331,7 @@ class BaseResponseMany(BaseResponse):
|
|
|
327
331
|
result (List[BaseModel] | None): The result of the response.
|
|
328
332
|
"""
|
|
329
333
|
|
|
330
|
-
count: int
|
|
334
|
+
count: int | None = None
|
|
331
335
|
ids: list[PRIMARY_KEY]
|
|
332
336
|
list_columns: list[str] = []
|
|
333
337
|
list_title: str = ""
|
|
@@ -346,45 +350,60 @@ class GeneralResponse(BaseModel):
|
|
|
346
350
|
detail: str
|
|
347
351
|
|
|
348
352
|
|
|
349
|
-
class
|
|
353
|
+
class OprFilterSchema(BaseModel):
|
|
350
354
|
"""
|
|
351
|
-
Represents a filter for querying data.
|
|
355
|
+
Represents a filter for querying data without column name.
|
|
352
356
|
|
|
353
357
|
Attributes:
|
|
354
|
-
col (str): The column name to filter on.
|
|
355
358
|
opr (str): The operator to use for the filter.
|
|
356
359
|
value (Union[PRIMARY_KEY, bool, Dict[str, Any], List[Union[PRIMARY_KEY, bool, Dict[str, Any]]]]): The value to filter on.
|
|
357
|
-
|
|
358
360
|
"""
|
|
359
361
|
|
|
360
|
-
col: str
|
|
361
362
|
opr: str
|
|
362
363
|
value: (
|
|
363
364
|
PRIMARY_KEY | bool | dict[str, Any] | list[PRIMARY_KEY | bool | dict[str, Any]]
|
|
364
365
|
)
|
|
365
366
|
|
|
366
367
|
|
|
368
|
+
class FilterSchema(OprFilterSchema):
|
|
369
|
+
"""
|
|
370
|
+
Represents a filter for querying data.
|
|
371
|
+
|
|
372
|
+
Attributes:
|
|
373
|
+
col (str): The column name to filter on.
|
|
374
|
+
opr (str): The operator to use for the filter.
|
|
375
|
+
value (Union[PRIMARY_KEY, bool, Dict[str, Any], List[Union[PRIMARY_KEY, bool, Dict[str, Any]]]]): The value to filter on.
|
|
376
|
+
|
|
377
|
+
"""
|
|
378
|
+
|
|
379
|
+
col: str
|
|
380
|
+
|
|
381
|
+
|
|
367
382
|
class QuerySchema(BaseModel):
|
|
368
383
|
"""
|
|
369
384
|
Represents the query parameters for selection, pagination, ordering, and filtering.
|
|
370
385
|
|
|
371
386
|
Attributes:
|
|
372
|
-
columns (str, optional): The columns to select in the query. Defaults to '[]'.
|
|
373
387
|
page (int): The page number for pagination. Defaults to 0.
|
|
374
388
|
page_size (int, optional): The number of items per page. Defaults to 25.
|
|
375
389
|
order_column (str, optional): The column to order the results by.
|
|
376
390
|
order_direction (Literal['asc', 'desc'], optional): The direction of the ordering (asc or desc).
|
|
391
|
+
columns (str, optional): The columns to select in the query. Defaults to '[]'.
|
|
377
392
|
filters (str, optional): The filters to apply to the query. Defaults to '[]'.
|
|
393
|
+
opr_filters (str, optional): The filters to apply without column name. Defaults to '[]'.
|
|
378
394
|
global_filter (str, optional): The filter to apply globally across all columns.
|
|
395
|
+
with_count (bool): Whether to include the total count of items. Defaults to True.
|
|
379
396
|
"""
|
|
380
397
|
|
|
381
|
-
columns: str = "[]"
|
|
382
398
|
page: int = 0
|
|
383
399
|
page_size: int | None = 25
|
|
384
400
|
order_column: str | None = None
|
|
385
401
|
order_direction: Literal["asc", "desc"] | None = None
|
|
402
|
+
columns: str = "[]"
|
|
386
403
|
filters: str = "[]"
|
|
404
|
+
opr_filters: str = "[]"
|
|
387
405
|
global_filter: str | None = None
|
|
406
|
+
with_count: bool = True
|
|
388
407
|
|
|
389
408
|
@field_validator("columns")
|
|
390
409
|
@classmethod
|
|
@@ -394,30 +413,48 @@ class QuerySchema(BaseModel):
|
|
|
394
413
|
columns = json.loads(v) if isinstance(v, str) else v
|
|
395
414
|
logger.debug(f"Columns: {columns}")
|
|
396
415
|
if not isinstance(columns, list):
|
|
397
|
-
raise
|
|
416
|
+
raise HTTPWithValidationException(
|
|
417
|
+
fastapi.status.HTTP_400_BAD_REQUEST,
|
|
418
|
+
"list_type",
|
|
419
|
+
"query",
|
|
420
|
+
"columns",
|
|
421
|
+
translate("Columns must be a list"),
|
|
422
|
+
input=columns,
|
|
423
|
+
)
|
|
398
424
|
return [str(col) for col in columns]
|
|
399
425
|
except json.JSONDecodeError:
|
|
400
|
-
raise
|
|
401
|
-
|
|
426
|
+
raise HTTPWithValidationException(
|
|
427
|
+
fastapi.status.HTTP_400_BAD_REQUEST,
|
|
428
|
+
"json_invalid",
|
|
429
|
+
"query",
|
|
430
|
+
"columns",
|
|
431
|
+
translate("Invalid columns: Not a valid JSON string"),
|
|
432
|
+
input=v,
|
|
433
|
+
)
|
|
434
|
+
except ValidationError as e:
|
|
435
|
+
raise HTTPWithValidationException(
|
|
436
|
+
fastapi.status.HTTP_400_BAD_REQUEST,
|
|
437
|
+
"literal_error",
|
|
438
|
+
"query",
|
|
439
|
+
"columns",
|
|
440
|
+
e.errors(),
|
|
402
441
|
)
|
|
403
|
-
except ValueError as e:
|
|
404
|
-
raise HTTPException(status_code=400, detail=str(e))
|
|
405
442
|
|
|
406
443
|
@field_validator("order_direction")
|
|
407
444
|
@classmethod
|
|
408
445
|
def validate_order_fields(cls, v: str | None, info: ValidationInfo):
|
|
409
446
|
data = info.data
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
detail="Both order_column and order_direction must be filled or empty",
|
|
447
|
+
if bool(v) != bool(data.get("order_column")):
|
|
448
|
+
raise HTTPWithValidationException(
|
|
449
|
+
fastapi.status.HTTP_400_BAD_REQUEST,
|
|
450
|
+
"missing",
|
|
451
|
+
"query",
|
|
452
|
+
"order_direction" if not v else "order_column",
|
|
453
|
+
translate(
|
|
454
|
+
"Both 'order_column' and 'order_direction' must be filled or empty"
|
|
455
|
+
),
|
|
420
456
|
)
|
|
457
|
+
return str(v) if v else v
|
|
421
458
|
|
|
422
459
|
@field_validator("filters")
|
|
423
460
|
@classmethod
|
|
@@ -427,31 +464,97 @@ class QuerySchema(BaseModel):
|
|
|
427
464
|
filters = json.loads(v) if isinstance(v, str) else v
|
|
428
465
|
logger.debug(f"Filters: {filters}")
|
|
429
466
|
if not isinstance(filters, list):
|
|
430
|
-
raise
|
|
467
|
+
raise HTTPWithValidationException(
|
|
468
|
+
fastapi.status.HTTP_400_BAD_REQUEST,
|
|
469
|
+
"list_type",
|
|
470
|
+
"query",
|
|
471
|
+
"filters",
|
|
472
|
+
translate("Filters must be a list"),
|
|
473
|
+
input=filters,
|
|
474
|
+
)
|
|
431
475
|
new_filters = []
|
|
432
|
-
for filter in filters:
|
|
476
|
+
for index, filter in enumerate(filters):
|
|
433
477
|
if isinstance(filter, FilterSchema):
|
|
434
478
|
new_filters.append(filter)
|
|
435
479
|
elif isinstance(filter, dict):
|
|
436
480
|
new_filters.append(FilterSchema(**filter))
|
|
437
481
|
else:
|
|
438
|
-
raise
|
|
482
|
+
raise HTTPWithValidationException(
|
|
483
|
+
fastapi.status.HTTP_400_BAD_REQUEST,
|
|
484
|
+
"dict_type",
|
|
485
|
+
"query",
|
|
486
|
+
f"filters[{index}]",
|
|
487
|
+
translate("Filter must be an object"),
|
|
488
|
+
input=filter,
|
|
489
|
+
)
|
|
490
|
+
return new_filters
|
|
491
|
+
except json.JSONDecodeError:
|
|
492
|
+
raise HTTPWithValidationException(
|
|
493
|
+
fastapi.status.HTTP_400_BAD_REQUEST,
|
|
494
|
+
"json_invalid",
|
|
495
|
+
"query",
|
|
496
|
+
"filters",
|
|
497
|
+
translate("Invalid filters: Not a valid JSON string"),
|
|
498
|
+
input=v,
|
|
499
|
+
)
|
|
500
|
+
except ValidationError as e:
|
|
501
|
+
raise HTTPWithValidationException(
|
|
502
|
+
fastapi.status.HTTP_400_BAD_REQUEST,
|
|
503
|
+
"literal_error",
|
|
504
|
+
"query",
|
|
505
|
+
"filters",
|
|
506
|
+
e.errors(),
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
@field_validator("opr_filters")
|
|
510
|
+
@classmethod
|
|
511
|
+
def validate_opr_filters(cls, v: str | list[OprFilterSchema]):
|
|
512
|
+
try:
|
|
513
|
+
logger.debug(f"Validating opr_filters: {v}")
|
|
514
|
+
filters = json.loads(v) if isinstance(v, str) else v
|
|
515
|
+
logger.debug(f"Opr Filters: {filters}")
|
|
516
|
+
if not isinstance(filters, list):
|
|
517
|
+
raise HTTPWithValidationException(
|
|
518
|
+
fastapi.status.HTTP_400_BAD_REQUEST,
|
|
519
|
+
"list_type",
|
|
520
|
+
"query",
|
|
521
|
+
"opr_filters",
|
|
522
|
+
translate("Opr Filters must be a list"),
|
|
523
|
+
input=filters,
|
|
524
|
+
)
|
|
525
|
+
new_filters = []
|
|
526
|
+
for index, filter in enumerate(filters):
|
|
527
|
+
if isinstance(filter, OprFilterSchema):
|
|
528
|
+
new_filters.append(filter)
|
|
529
|
+
elif isinstance(filter, dict):
|
|
530
|
+
new_filters.append(OprFilterSchema(**filter))
|
|
531
|
+
else:
|
|
532
|
+
raise HTTPWithValidationException(
|
|
533
|
+
fastapi.status.HTTP_400_BAD_REQUEST,
|
|
534
|
+
"dict_type",
|
|
535
|
+
"query",
|
|
536
|
+
f"opr_filters[{index}]",
|
|
537
|
+
translate("Opr Filter must be an object"),
|
|
538
|
+
input=filter,
|
|
539
|
+
)
|
|
439
540
|
return new_filters
|
|
440
541
|
except json.JSONDecodeError:
|
|
441
|
-
raise
|
|
442
|
-
|
|
542
|
+
raise HTTPWithValidationException(
|
|
543
|
+
fastapi.status.HTTP_400_BAD_REQUEST,
|
|
544
|
+
"json_invalid",
|
|
545
|
+
"query",
|
|
546
|
+
"opr_filters",
|
|
547
|
+
translate("Invalid opr_filters: Not a valid JSON string"),
|
|
548
|
+
input=v,
|
|
443
549
|
)
|
|
444
550
|
except ValidationError as e:
|
|
445
|
-
raise
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
},
|
|
551
|
+
raise HTTPWithValidationException(
|
|
552
|
+
fastapi.status.HTTP_400_BAD_REQUEST,
|
|
553
|
+
"literal_error",
|
|
554
|
+
"query",
|
|
555
|
+
"opr_filters",
|
|
556
|
+
e.errors(),
|
|
452
557
|
)
|
|
453
|
-
except ValueError as e:
|
|
454
|
-
raise HTTPException(status_code=400, detail=str(e))
|
|
455
558
|
|
|
456
559
|
|
|
457
560
|
class QueryBody(QuerySchema):
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
|
+
import sqlalchemy
|
|
3
4
|
from fastapi import Depends, HTTPException, Request, status
|
|
4
5
|
from pydantic import BaseModel, EmailStr, Field
|
|
5
6
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
@@ -7,9 +8,11 @@ from sqlalchemy.orm import Session
|
|
|
7
8
|
|
|
8
9
|
from ...api import BaseApi, ModelRestApi
|
|
9
10
|
from ...backends.sqla.interface import SQLAInterface
|
|
10
|
-
from ...
|
|
11
|
+
from ...const import ErrorCode
|
|
12
|
+
from ...db import UserDatabase, db, get_user_db
|
|
11
13
|
from ...decorators import expose, login_required
|
|
12
14
|
from ...globals import g
|
|
15
|
+
from ...lang import translate
|
|
13
16
|
from ...routers import get_auth_router, get_oauth_router
|
|
14
17
|
from ...schemas import (
|
|
15
18
|
GeneralResponse,
|
|
@@ -19,7 +22,7 @@ from ...schemas import (
|
|
|
19
22
|
generate_user_update_schema,
|
|
20
23
|
)
|
|
21
24
|
from ...setting import Setting
|
|
22
|
-
from ...utils import SelfType, lazy, merge_schema
|
|
25
|
+
from ...utils import SelfType, lazy, merge_schema, smart_run
|
|
23
26
|
from .models import Api, Permission, PermissionApi, Role, User
|
|
24
27
|
|
|
25
28
|
__all__ = [
|
|
@@ -161,7 +164,7 @@ class UsersApi(ModelRestApi):
|
|
|
161
164
|
col.required = False
|
|
162
165
|
|
|
163
166
|
async def post_add(self, item, params):
|
|
164
|
-
await g.current_app.
|
|
167
|
+
await g.current_app.sm.update_user(
|
|
165
168
|
user_update={"password": item.password},
|
|
166
169
|
user=item,
|
|
167
170
|
session=params.session,
|
|
@@ -170,7 +173,7 @@ class UsersApi(ModelRestApi):
|
|
|
170
173
|
|
|
171
174
|
async def post_update(self, item, params):
|
|
172
175
|
if params.body.password:
|
|
173
|
-
await g.current_app.
|
|
176
|
+
await g.current_app.sm.update_user(
|
|
174
177
|
user_update={"password": params.body.password},
|
|
175
178
|
user=item,
|
|
176
179
|
session=params.session,
|
|
@@ -194,6 +197,7 @@ class AuthApi(BaseApi):
|
|
|
194
197
|
g.auth.cookie_backend,
|
|
195
198
|
g.auth.fastapi_users.get_user_manager,
|
|
196
199
|
g.auth.fastapi_users.authenticator,
|
|
200
|
+
auth_type=Setting.AUTH_TYPE,
|
|
197
201
|
)
|
|
198
202
|
)
|
|
199
203
|
if Setting.AUTH_LOGIN_JWT:
|
|
@@ -202,6 +206,7 @@ class AuthApi(BaseApi):
|
|
|
202
206
|
g.auth.bearer_backend,
|
|
203
207
|
g.auth.fastapi_users.get_user_manager,
|
|
204
208
|
g.auth.fastapi_users.authenticator,
|
|
209
|
+
auth_type=Setting.AUTH_TYPE,
|
|
205
210
|
),
|
|
206
211
|
prefix="/jwt",
|
|
207
212
|
)
|
|
@@ -225,6 +230,11 @@ class AuthApi(BaseApi):
|
|
|
225
230
|
for client in oauth_clients:
|
|
226
231
|
oauth_client = client["oauth_client"]
|
|
227
232
|
associate_by_email = client.get("associate_by_email", False)
|
|
233
|
+
redirect_url = client.get("redirect_url", None)
|
|
234
|
+
redirect_url_factory = client.get("redirect_url_factory", None)
|
|
235
|
+
redirect_url_after_callback = client.get(
|
|
236
|
+
"redirect_url_after_callback", Setting.OAUTH_REDIRECT_URI
|
|
237
|
+
)
|
|
228
238
|
on_before_register = client.get("on_before_register", None)
|
|
229
239
|
on_after_register = client.get("on_after_register", None)
|
|
230
240
|
on_before_login = client.get("on_before_login", None)
|
|
@@ -235,7 +245,9 @@ class AuthApi(BaseApi):
|
|
|
235
245
|
backend=g.auth.cookie_backend,
|
|
236
246
|
get_user_manager=g.auth.fastapi_users.get_user_manager,
|
|
237
247
|
state_secret=g.auth.secret_key,
|
|
238
|
-
redirect_url=
|
|
248
|
+
redirect_url=redirect_url,
|
|
249
|
+
redirect_url_factory=redirect_url_factory,
|
|
250
|
+
redirect_url_after_callback=redirect_url_after_callback,
|
|
239
251
|
associate_by_email=associate_by_email,
|
|
240
252
|
on_before_register=on_before_register,
|
|
241
253
|
on_after_register=on_after_register,
|
|
@@ -250,7 +262,7 @@ class AuthApi(BaseApi):
|
|
|
250
262
|
response_model=self.get_user_schema,
|
|
251
263
|
responses={
|
|
252
264
|
status.HTTP_401_UNAUTHORIZED: {
|
|
253
|
-
"description":
|
|
265
|
+
"description": ErrorCode.GET_USER_MISSING_TOKEN_OR_INACTIVE_USER,
|
|
254
266
|
}
|
|
255
267
|
},
|
|
256
268
|
)(self.get_user)
|
|
@@ -259,17 +271,31 @@ class AuthApi(BaseApi):
|
|
|
259
271
|
methods=["PUT"],
|
|
260
272
|
responses={
|
|
261
273
|
status.HTTP_401_UNAUTHORIZED: {
|
|
262
|
-
"description":
|
|
274
|
+
"description": ErrorCode.GET_USER_MISSING_TOKEN_OR_INACTIVE_USER,
|
|
263
275
|
}
|
|
264
276
|
},
|
|
265
277
|
)(self.update_user)
|
|
266
278
|
|
|
267
|
-
def get_user(self):
|
|
279
|
+
async def get_user(self):
|
|
268
280
|
if not g.user:
|
|
269
281
|
raise HTTPException(
|
|
270
|
-
|
|
271
|
-
|
|
282
|
+
status.HTTP_401_UNAUTHORIZED,
|
|
283
|
+
ErrorCode.GET_USER_MISSING_TOKEN_OR_INACTIVE_USER,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
g.user.permissions = []
|
|
287
|
+
if g.user.roles:
|
|
288
|
+
# Retrieve list of api names that user has access to
|
|
289
|
+
query = (
|
|
290
|
+
sqlalchemy.select(Api.name)
|
|
291
|
+
.join(PermissionApi)
|
|
292
|
+
.join(PermissionApi.roles)
|
|
293
|
+
.where(Role.id.in_([role.id for role in g.user.roles]))
|
|
294
|
+
.distinct()
|
|
272
295
|
)
|
|
296
|
+
result = await smart_run(db.current_session.scalars, query)
|
|
297
|
+
g.user.permissions = list(result)
|
|
298
|
+
|
|
273
299
|
return g.user
|
|
274
300
|
|
|
275
301
|
async def update_user(
|
|
@@ -280,9 +306,9 @@ class AuthApi(BaseApi):
|
|
|
280
306
|
):
|
|
281
307
|
if not g.user:
|
|
282
308
|
raise HTTPException(
|
|
283
|
-
|
|
284
|
-
|
|
309
|
+
status.HTTP_401_UNAUTHORIZED,
|
|
310
|
+
ErrorCode.GET_USER_MISSING_TOKEN_OR_INACTIVE_USER,
|
|
285
311
|
)
|
|
286
312
|
user_manager = next(g.auth.get_user_manager(user_db))
|
|
287
313
|
await user_manager.update(user_update, g.user, safe=True, request=request)
|
|
288
|
-
return GeneralResponse(detail="User updated successfully.")
|
|
314
|
+
return GeneralResponse(detail=translate("User updated successfully."))
|
|
@@ -77,21 +77,18 @@ class PermissionApi(Model):
|
|
|
77
77
|
Integer, ForeignKey(f"{API_TABLE}.id"), name=view_menu_id, nullable=False
|
|
78
78
|
)
|
|
79
79
|
api: Mapped["Api"] = relationship(
|
|
80
|
-
"Api", back_populates="permissions", lazy="
|
|
80
|
+
"Api", back_populates="permissions", lazy="joined"
|
|
81
81
|
)
|
|
82
82
|
|
|
83
83
|
permission_id: Mapped[int] = mapped_column(
|
|
84
84
|
Integer, ForeignKey(f"{PERMISSION_TABLE}.id"), nullable=False
|
|
85
85
|
)
|
|
86
86
|
permission: Mapped["Permission"] = relationship(
|
|
87
|
-
"Permission", back_populates="apis", lazy="
|
|
87
|
+
"Permission", back_populates="apis", lazy="joined"
|
|
88
88
|
)
|
|
89
89
|
|
|
90
90
|
roles: Mapped[list["Role"]] = relationship(
|
|
91
|
-
"Role",
|
|
92
|
-
secondary=assoc_permission_api_role,
|
|
93
|
-
back_populates="permissions",
|
|
94
|
-
lazy="selectin",
|
|
91
|
+
"Role", secondary=assoc_permission_api_role, back_populates="permissions"
|
|
95
92
|
)
|
|
96
93
|
|
|
97
94
|
def __repr__(self) -> str:
|
|
@@ -104,10 +101,7 @@ class Api(Model):
|
|
|
104
101
|
name: Mapped[str] = mapped_column(String, unique=True, nullable=False)
|
|
105
102
|
|
|
106
103
|
permissions: Mapped[list[PermissionApi]] = relationship(
|
|
107
|
-
PermissionApi,
|
|
108
|
-
back_populates="api",
|
|
109
|
-
lazy="selectin",
|
|
110
|
-
cascade="all, delete-orphan",
|
|
104
|
+
PermissionApi, back_populates="api", cascade="all, delete-orphan"
|
|
111
105
|
)
|
|
112
106
|
|
|
113
107
|
def __eq__(self, other):
|
|
@@ -128,10 +122,7 @@ class Permission(Model):
|
|
|
128
122
|
name: Mapped[str] = mapped_column(String, unique=True, nullable=False)
|
|
129
123
|
|
|
130
124
|
apis: Mapped[list[PermissionApi]] = relationship(
|
|
131
|
-
PermissionApi,
|
|
132
|
-
back_populates="permission",
|
|
133
|
-
lazy="selectin",
|
|
134
|
-
cascade="all, delete-orphan",
|
|
125
|
+
PermissionApi, back_populates="permission", cascade="all, delete-orphan"
|
|
135
126
|
)
|
|
136
127
|
|
|
137
128
|
def __repr__(self) -> str:
|
|
@@ -144,14 +135,11 @@ class Role(Model):
|
|
|
144
135
|
name: Mapped[str] = mapped_column(String, unique=True, nullable=False)
|
|
145
136
|
|
|
146
137
|
users: Mapped[list["User"]] = relationship(
|
|
147
|
-
"User", secondary=assoc_user_role, back_populates="roles"
|
|
138
|
+
"User", secondary=assoc_user_role, back_populates="roles"
|
|
148
139
|
)
|
|
149
140
|
|
|
150
141
|
permissions: Mapped[list[PermissionApi]] = relationship(
|
|
151
|
-
PermissionApi,
|
|
152
|
-
secondary=assoc_permission_api_role,
|
|
153
|
-
back_populates="roles",
|
|
154
|
-
lazy="selectin",
|
|
142
|
+
PermissionApi, secondary=assoc_permission_api_role, back_populates="roles"
|
|
155
143
|
)
|
|
156
144
|
|
|
157
145
|
def __repr__(self) -> str:
|
|
@@ -203,10 +191,7 @@ class User(Model):
|
|
|
203
191
|
return Column(Integer, ForeignKey("ab_user.id"), default=self.get_user_id)
|
|
204
192
|
|
|
205
193
|
oauth_accounts: Mapped[list[OAuthAccount]] = relationship(
|
|
206
|
-
"OAuthAccount",
|
|
207
|
-
back_populates="user",
|
|
208
|
-
lazy="selectin",
|
|
209
|
-
cascade="all, delete-orphan",
|
|
194
|
+
"OAuthAccount", back_populates="user", cascade="all, delete-orphan"
|
|
210
195
|
)
|
|
211
196
|
|
|
212
197
|
roles: Mapped[list[Role]] = relationship(
|