shaapi 0.1.0__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.
- shaapi/__init__.py +3 -0
- shaapi/cli.py +97 -0
- shaapi/generator.py +114 -0
- shaapi/template/.dockerignore +37 -0
- shaapi/template/.env.template +59 -0
- shaapi/template/.gitattributes +12 -0
- shaapi/template/.gitignore +170 -0
- shaapi/template/.gitlab-ci.yml +89 -0
- shaapi/template/Dockerfile +59 -0
- shaapi/template/LICENSE +21 -0
- shaapi/template/README.md +206 -0
- shaapi/template/backend/.gitignore +164 -0
- shaapi/template/backend/__init__.py +0 -0
- shaapi/template/backend/alembic/README +1 -0
- shaapi/template/backend/alembic/env.py +102 -0
- shaapi/template/backend/alembic/script.py.mako +26 -0
- shaapi/template/backend/alembic/versions/2026_06_08_1024-64524c63b666_initial.py +143 -0
- shaapi/template/backend/alembic.ini +117 -0
- shaapi/template/backend/app/__init__.py +55 -0
- shaapi/template/backend/app/admin/__init__.py +1 -0
- shaapi/template/backend/app/admin/api/v1/__init__.py +0 -0
- shaapi/template/backend/app/admin/api/v1/auth.py +59 -0
- shaapi/template/backend/app/admin/api/v1/casbin.py +218 -0
- shaapi/template/backend/app/admin/api/v1/login_log.py +63 -0
- shaapi/template/backend/app/admin/api/v1/opera_log.py +61 -0
- shaapi/template/backend/app/admin/api/v1/role.py +108 -0
- shaapi/template/backend/app/admin/api/v1/user.py +47 -0
- shaapi/template/backend/app/admin/schema/casbin_rule.py +45 -0
- shaapi/template/backend/app/admin/schema/login_log.py +36 -0
- shaapi/template/backend/app/admin/schema/opera_log.py +43 -0
- shaapi/template/backend/app/admin/schema/role.py +36 -0
- shaapi/template/backend/app/admin/schema/sso.py +37 -0
- shaapi/template/backend/app/admin/schema/token.py +74 -0
- shaapi/template/backend/app/admin/schema/user.py +93 -0
- shaapi/template/backend/app/admin/service/auth_service.py +233 -0
- shaapi/template/backend/app/admin/service/casbin_service.py +135 -0
- shaapi/template/backend/app/admin/service/login_log_service.py +62 -0
- shaapi/template/backend/app/admin/service/opera_log_service.py +31 -0
- shaapi/template/backend/app/admin/service/role_service.py +79 -0
- shaapi/template/backend/app/admin/service/secure_token_service.py +60 -0
- shaapi/template/backend/app/admin/service/user_service.py +153 -0
- shaapi/template/backend/app/api.py +11 -0
- shaapi/template/backend/common/__init__.py +0 -0
- shaapi/template/backend/common/cloud_storage/__init__.py +11 -0
- shaapi/template/backend/common/cloud_storage/cloud_storage.py +180 -0
- shaapi/template/backend/common/dataclasses.py +52 -0
- shaapi/template/backend/common/email_conf/email.py +105 -0
- shaapi/template/backend/common/enums.py +144 -0
- shaapi/template/backend/common/exception/__init__.py +0 -0
- shaapi/template/backend/common/exception/errors.py +87 -0
- shaapi/template/backend/common/exception/exception_handler.py +280 -0
- shaapi/template/backend/common/log.py +123 -0
- shaapi/template/backend/common/model.py +68 -0
- shaapi/template/backend/common/pagination.py +83 -0
- shaapi/template/backend/common/response/__init__.py +0 -0
- shaapi/template/backend/common/response/response_code.py +158 -0
- shaapi/template/backend/common/response/response_schema.py +110 -0
- shaapi/template/backend/common/schema.py +144 -0
- shaapi/template/backend/common/security/jwt.py +203 -0
- shaapi/template/backend/common/security/rbac.py +98 -0
- shaapi/template/backend/common/security/sec_token.py +6 -0
- shaapi/template/backend/common/socketio/action.py +11 -0
- shaapi/template/backend/common/socketio/server.py +50 -0
- shaapi/template/backend/common/sso/base.py +69 -0
- shaapi/template/backend/common/sso/google.py +127 -0
- shaapi/template/backend/core/conf.py +208 -0
- shaapi/template/backend/core/path_conf.py +24 -0
- shaapi/template/backend/core/registrar.py +195 -0
- shaapi/template/backend/crud/__init__.py +1 -0
- shaapi/template/backend/crud/crud_base.py +35 -0
- shaapi/template/backend/crud/crud_casbin.py +46 -0
- shaapi/template/backend/crud/crud_login_log.py +58 -0
- shaapi/template/backend/crud/crud_opera_log.py +58 -0
- shaapi/template/backend/crud/crud_role.py +128 -0
- shaapi/template/backend/crud/crud_user.py +267 -0
- shaapi/template/backend/database/__init__.py +0 -0
- shaapi/template/backend/database/db_postgres.py +125 -0
- shaapi/template/backend/database/db_redis.py +62 -0
- shaapi/template/backend/entrypoint-api.sh +19 -0
- shaapi/template/backend/lang/en/app.py +18 -0
- shaapi/template/backend/lang/en/auth.py +10 -0
- shaapi/template/backend/lang/fr/app.py +18 -0
- shaapi/template/backend/lang/fr/auth.py +10 -0
- shaapi/template/backend/main.py +54 -0
- shaapi/template/backend/middleware/__init__.py +1 -0
- shaapi/template/backend/middleware/access_middleware.py +19 -0
- shaapi/template/backend/middleware/i18n_middleware.py +19 -0
- shaapi/template/backend/middleware/jwt_auth_middleware.py +73 -0
- shaapi/template/backend/middleware/opera_log_middleware.py +179 -0
- shaapi/template/backend/middleware/state_middleware.py +26 -0
- shaapi/template/backend/models/__init__.py +10 -0
- shaapi/template/backend/models/associations.py +20 -0
- shaapi/template/backend/models/casbin_rule.py +30 -0
- shaapi/template/backend/models/login_log.py +28 -0
- shaapi/template/backend/models/opera_log.py +36 -0
- shaapi/template/backend/models/role.py +27 -0
- shaapi/template/backend/models/user.py +30 -0
- shaapi/template/backend/seeder/json/admin.json +15 -0
- shaapi/template/backend/seeder/json/user.json +15 -0
- shaapi/template/backend/seeder/run.py +34 -0
- shaapi/template/backend/static/ip2region.xdb +0 -0
- shaapi/template/backend/templates/build/meet.html +169 -0
- shaapi/template/backend/templates/build/new_account.html +373 -0
- shaapi/template/backend/templates/build/reset-password.html +170 -0
- shaapi/template/backend/templates/build/test_email.html +25 -0
- shaapi/template/backend/templates/build/welcome-one-1.html +160 -0
- shaapi/template/backend/templates/build/welcome-one.html +178 -0
- shaapi/template/backend/templates/build/welcome-two.html +234 -0
- shaapi/template/backend/templates/index.html +0 -0
- shaapi/template/backend/templates/src/new_account.mjml +15 -0
- shaapi/template/backend/templates/src/reset_password.mjml +19 -0
- shaapi/template/backend/templates/src/test_email.mjml +11 -0
- shaapi/template/backend/templates/ws/ws.html +70 -0
- shaapi/template/backend/utils/demo_site.py +18 -0
- shaapi/template/backend/utils/encrypt.py +108 -0
- shaapi/template/backend/utils/health_check.py +34 -0
- shaapi/template/backend/utils/prometheus.py +135 -0
- shaapi/template/backend/utils/request_parse.py +110 -0
- shaapi/template/backend/utils/serializers.py +75 -0
- shaapi/template/backend/utils/timezone.py +51 -0
- shaapi/template/backend/utils/trace_id.py +7 -0
- shaapi/template/backend/utils/translator.py +28 -0
- shaapi/template/devops/scripts/deploy.sh +7 -0
- shaapi/template/devops/scripts/setup_env.sh +62 -0
- shaapi/template/docker-compose.monitoring.yml +63 -0
- shaapi/template/docker-compose.override.yml +12 -0
- shaapi/template/docker-compose.yml +90 -0
- shaapi/template/docker-run.sh +99 -0
- shaapi/template/etc/dashboards/fastapi-observability.json +1044 -0
- shaapi/template/etc/dashboards.yaml +10 -0
- shaapi/template/etc/grafana/datasource.yml +79 -0
- shaapi/template/etc/prometheus/prometheus.yml +52 -0
- shaapi/template/package-lock.json +2102 -0
- shaapi/template/package.json +16 -0
- shaapi/template/pyproject.toml +78 -0
- shaapi/template/uv.lock +2866 -0
- shaapi-0.1.0.dist-info/METADATA +92 -0
- shaapi-0.1.0.dist-info/RECORD +141 -0
- shaapi-0.1.0.dist-info/WHEEL +4 -0
- shaapi-0.1.0.dist-info/entry_points.txt +2 -0
- shaapi-0.1.0.dist-info/licenses/LICENCE +21 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# A generic, single database configuration.
|
|
2
|
+
|
|
3
|
+
[alembic]
|
|
4
|
+
# path to migration scripts
|
|
5
|
+
# Use forward slashes (/) also on windows to provide an os agnostic path
|
|
6
|
+
script_location = alembic
|
|
7
|
+
|
|
8
|
+
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
|
|
9
|
+
# Uncomment the line below if you want the files to be prepended with date and time
|
|
10
|
+
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
|
|
11
|
+
# for all available tokens
|
|
12
|
+
file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
|
|
13
|
+
|
|
14
|
+
# sys.path path, will be prepended to sys.path if present.
|
|
15
|
+
# defaults to the current working directory.
|
|
16
|
+
prepend_sys_path = .
|
|
17
|
+
|
|
18
|
+
# timezone to use when rendering the date within the migration file
|
|
19
|
+
# as well as the filename.
|
|
20
|
+
# If specified, requires the python>=3.9 or backports.zoneinfo library.
|
|
21
|
+
# Any required deps can installed by adding `alembic[tz]` to the pip requirements
|
|
22
|
+
# string value is passed to ZoneInfo()
|
|
23
|
+
# leave blank for localtime
|
|
24
|
+
# timezone =
|
|
25
|
+
|
|
26
|
+
# max length of characters to apply to the "slug" field
|
|
27
|
+
# truncate_slug_length = 40
|
|
28
|
+
|
|
29
|
+
# set to 'true' to run the environment during
|
|
30
|
+
# the 'revision' command, regardless of autogenerate
|
|
31
|
+
# revision_environment = false
|
|
32
|
+
|
|
33
|
+
# set to 'true' to allow .pyc and .pyo files without
|
|
34
|
+
# a source .py file to be detected as revisions in the
|
|
35
|
+
# versions/ directory
|
|
36
|
+
# sourceless = false
|
|
37
|
+
|
|
38
|
+
# version location specification; This defaults
|
|
39
|
+
# to alembic/versions. When using multiple version
|
|
40
|
+
# directories, initial revisions must be specified with --version-path.
|
|
41
|
+
# The path separator used here should be the separator specified by "version_path_separator" below.
|
|
42
|
+
# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions
|
|
43
|
+
|
|
44
|
+
# version path separator; As mentioned above, this is the character used to split
|
|
45
|
+
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
|
|
46
|
+
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
|
|
47
|
+
# Valid values for version_path_separator are:
|
|
48
|
+
#
|
|
49
|
+
# version_path_separator = :
|
|
50
|
+
# version_path_separator = ;
|
|
51
|
+
# version_path_separator = space
|
|
52
|
+
# version_path_separator = newline
|
|
53
|
+
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
|
|
54
|
+
|
|
55
|
+
# set to 'true' to search source files recursively
|
|
56
|
+
# in each "version_locations" directory
|
|
57
|
+
# new in Alembic version 1.10
|
|
58
|
+
# recursive_version_locations = false
|
|
59
|
+
|
|
60
|
+
# the output encoding used when revision files
|
|
61
|
+
# are written from script.py.mako
|
|
62
|
+
# output_encoding = utf-8
|
|
63
|
+
|
|
64
|
+
sqlalchemy.url = driver://user:pass@localhost/dbname
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
[post_write_hooks]
|
|
68
|
+
# post_write_hooks defines scripts or Python functions that are run
|
|
69
|
+
# on newly generated revision scripts. See the documentation for further
|
|
70
|
+
# detail and examples
|
|
71
|
+
|
|
72
|
+
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
|
73
|
+
# hooks = black
|
|
74
|
+
# black.type = console_scripts
|
|
75
|
+
# black.entrypoint = black
|
|
76
|
+
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
|
77
|
+
|
|
78
|
+
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
|
|
79
|
+
# hooks = ruff
|
|
80
|
+
# ruff.type = exec
|
|
81
|
+
# ruff.executable = %(here)s/.venv/bin/ruff
|
|
82
|
+
# ruff.options = --fix REVISION_SCRIPT_FILENAME
|
|
83
|
+
|
|
84
|
+
# Logging configuration
|
|
85
|
+
[loggers]
|
|
86
|
+
keys = root,sqlalchemy,alembic
|
|
87
|
+
|
|
88
|
+
[handlers]
|
|
89
|
+
keys = console
|
|
90
|
+
|
|
91
|
+
[formatters]
|
|
92
|
+
keys = generic
|
|
93
|
+
|
|
94
|
+
[logger_root]
|
|
95
|
+
level = WARNING
|
|
96
|
+
handlers = console
|
|
97
|
+
qualname =
|
|
98
|
+
|
|
99
|
+
[logger_sqlalchemy]
|
|
100
|
+
level = WARNING
|
|
101
|
+
handlers =
|
|
102
|
+
qualname = sqlalchemy.engine
|
|
103
|
+
|
|
104
|
+
[logger_alembic]
|
|
105
|
+
level = INFO
|
|
106
|
+
handlers =
|
|
107
|
+
qualname = alembic
|
|
108
|
+
|
|
109
|
+
[handler_console]
|
|
110
|
+
class = StreamHandler
|
|
111
|
+
args = (sys.stderr,)
|
|
112
|
+
level = NOTSET
|
|
113
|
+
formatter = generic
|
|
114
|
+
|
|
115
|
+
[formatter_generic]
|
|
116
|
+
format = %(levelname)-5.5s [%(name)s] %(message)s
|
|
117
|
+
datefmt = %H:%M:%S
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import importlib
|
|
3
|
+
from typing import Iterator
|
|
4
|
+
import types
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
class Handlers:
|
|
11
|
+
handlers_base_path = ('backend', 'app')
|
|
12
|
+
ignored = ('__init__.py', '__pycache__','crud','schema','service')
|
|
13
|
+
paths = ('admin/api/v1', 'client/api/v1', 'mentor/api/v1', 'company/api/v1')
|
|
14
|
+
# include_dirs = ["v1"]
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def __all_module_paths(cls) -> list:
|
|
18
|
+
module_paths = []
|
|
19
|
+
|
|
20
|
+
for path in cls.paths:
|
|
21
|
+
handlers_path = f"{os.path.join(os.getcwd(), *cls.handlers_base_path)}/{path}/".replace("backend/backend","backend")
|
|
22
|
+
for root, dirs, files in os.walk(handlers_path):
|
|
23
|
+
for file in files:
|
|
24
|
+
if file.endswith('.py') and file not in cls.ignored:
|
|
25
|
+
relative_path = os.path.relpath(os.path.join(root, file), handlers_path)
|
|
26
|
+
module_paths.append(os.path.join(path, relative_path))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
return module_paths
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def __module_namespace(cls, module_path: str) -> str:
|
|
35
|
+
# Normalize both separators so module resolution works on Windows
|
|
36
|
+
# (os.sep == '\\') and Linux (os.sep == '/') alike.
|
|
37
|
+
module_path = module_path.replace(os.sep, '.').replace('/', '.').replace('.py', '')
|
|
38
|
+
namespace = '%s.%s' % ('.'.join(cls.handlers_base_path), module_path)
|
|
39
|
+
return namespace
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def iterator(cls) -> Iterator[types.ModuleType]:
|
|
43
|
+
for module_path in cls.__all_module_paths():
|
|
44
|
+
module_name = cls.__module_namespace(module_path)
|
|
45
|
+
try:
|
|
46
|
+
handler = importlib.import_module(module_name)
|
|
47
|
+
yield handler
|
|
48
|
+
except Exception as e:
|
|
49
|
+
logger.error(f"Erreur lors de l'importation de {module_name}: {e}")
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def modules(cls) -> map:
|
|
53
|
+
return map(
|
|
54
|
+
lambda module_path: cls.__module_namespace(module_path), cls.__all_module_paths()
|
|
55
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
File without changes
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from fastapi import APIRouter, Request, Response
|
|
2
|
+
from starlette.background import BackgroundTasks
|
|
3
|
+
|
|
4
|
+
from backend.app.admin.schema.user import (
|
|
5
|
+
GetCurrentUserInfoDetail,
|
|
6
|
+
# UpdatePasswordParam,
|
|
7
|
+
UserLoginSchema,
|
|
8
|
+
UserRegister
|
|
9
|
+
)
|
|
10
|
+
from backend.app.admin.service.auth_service import auth_service
|
|
11
|
+
# from backend.app.user.service.user_service import user_service
|
|
12
|
+
from backend.common.response.response_schema import ResponseModel, response_base
|
|
13
|
+
from backend.common.security.jwt import DependsJwtAuth
|
|
14
|
+
|
|
15
|
+
router = APIRouter(prefix="/auth", tags=["User Auth"])
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@router.post('/register', summary='registered user')
|
|
19
|
+
async def register_user(request: Request, response: Response, obj: UserRegister, background_tasks: BackgroundTasks) -> ResponseModel:
|
|
20
|
+
data = await auth_service.register(request=request, response=response, obj=obj, background_tasks=background_tasks)
|
|
21
|
+
|
|
22
|
+
return response_base.success(request=request, data=data)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@router.post(
|
|
26
|
+
'/login',
|
|
27
|
+
summary='user login',
|
|
28
|
+
description='json format, only supports debugging in third-party api tools, e.g. postman.',
|
|
29
|
+
# dependencies=[Depends(RateLimiter(times=5, minutes=1))],
|
|
30
|
+
)
|
|
31
|
+
async def user_login(
|
|
32
|
+
request: Request, response: Response, obj: UserLoginSchema, background_tasks: BackgroundTasks
|
|
33
|
+
) -> ResponseModel:
|
|
34
|
+
data = await auth_service.login(request=request, response=response, obj=obj, background_tasks=background_tasks)
|
|
35
|
+
return response_base.success(request=request, data=data)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@router.post('/token/new', summary='Create a new token', dependencies=[DependsJwtAuth])
|
|
39
|
+
async def create_new_token(request: Request, response: Response) -> ResponseModel:
|
|
40
|
+
data = await auth_service.new_token(request=request, response=response)
|
|
41
|
+
return response_base.success(request=request, data=data)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@router.post('/logout', summary='user logout', dependencies=[DependsJwtAuth])
|
|
45
|
+
async def user_logout(request: Request, response: Response) -> ResponseModel:
|
|
46
|
+
await auth_service.logout(request=request, response=response)
|
|
47
|
+
return response_base.success(request=request)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@router.get('/me', summary='Get connected user profile', dependencies=[DependsJwtAuth], response_model_exclude={'password'})
|
|
51
|
+
async def get_current_user(request: Request) -> ResponseModel:
|
|
52
|
+
data = GetCurrentUserInfoDetail(**request.user.model_dump())
|
|
53
|
+
return response_base.success(request=request, data=data)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
from typing import Annotated
|
|
2
|
+
from uuid import UUID
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter, Query, Request
|
|
5
|
+
|
|
6
|
+
from backend.app.admin.schema.casbin_rule import (
|
|
7
|
+
CreatePolicyParam,
|
|
8
|
+
CreateUserRoleParam,
|
|
9
|
+
DeleteAllPoliciesParam,
|
|
10
|
+
DeletePolicyParam,
|
|
11
|
+
DeleteUserRoleParam,
|
|
12
|
+
GetPolicyListDetails,
|
|
13
|
+
UpdatePolicyParam,
|
|
14
|
+
)
|
|
15
|
+
from backend.app.admin.service.casbin_service import casbin_service
|
|
16
|
+
from backend.common.pagination import DependsPagination, paging_data
|
|
17
|
+
from backend.common.response.response_schema import ResponseModel, response_base
|
|
18
|
+
from backend.common.security.jwt import DependsJwtAuth
|
|
19
|
+
from backend.common.security.rbac import DependsRBAC
|
|
20
|
+
from backend.database.db_postgres import CurrentSession
|
|
21
|
+
|
|
22
|
+
router = APIRouter(prefix='/casbin', tags=["Casbin"])
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@router.get(
|
|
26
|
+
'/',
|
|
27
|
+
summary='(Fuzzy condition) Get all permission policies with pagination',
|
|
28
|
+
dependencies=[
|
|
29
|
+
DependsJwtAuth,
|
|
30
|
+
DependsPagination,
|
|
31
|
+
],
|
|
32
|
+
)
|
|
33
|
+
async def get_pagination_casbin(
|
|
34
|
+
request: Request,
|
|
35
|
+
db: CurrentSession,
|
|
36
|
+
ptype: Annotated[str | None, Query(description='Policy type, p / g')] = None,
|
|
37
|
+
sub: Annotated[str | None, Query(description='User UUID / Role')] = None,
|
|
38
|
+
) :
|
|
39
|
+
casbin_select = await casbin_service.get_casbin_list(ptype=ptype, sub=sub)
|
|
40
|
+
page_data = await paging_data(db, casbin_select, GetPolicyListDetails)
|
|
41
|
+
return response_base.success(request=request, data=page_data)
|
|
42
|
+
|
|
43
|
+
@router.get('/policies', summary='Get all P permission policies', dependencies=[DependsJwtAuth])
|
|
44
|
+
async def get_all_policies(request: Request, role: Annotated[int | None, Query(description='Role ID')] = None) -> ResponseModel:
|
|
45
|
+
policies = await casbin_service.get_policy_list(role=role)
|
|
46
|
+
return response_base.success(request=request, data=policies)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@router.post(
|
|
50
|
+
'/policy',
|
|
51
|
+
summary='Add P permission policy',
|
|
52
|
+
dependencies=[
|
|
53
|
+
DependsRBAC,
|
|
54
|
+
],
|
|
55
|
+
)
|
|
56
|
+
async def create_policy(request: Request, p: CreatePolicyParam) -> ResponseModel:
|
|
57
|
+
"""
|
|
58
|
+
P Policy:
|
|
59
|
+
|
|
60
|
+
- It is recommended to add role-based access control, which needs to be combined with adding G policies to actually have access rights. Suitable for configuring global interface access policies.<br>
|
|
61
|
+
**Format**: Role + Access Path + Access Method
|
|
62
|
+
|
|
63
|
+
- If adding user-based access control, there is no need to add G policies to actually have access rights. Suitable for configuring specific user interface access policies.<br>
|
|
64
|
+
**Format**: User UUID + Access Path + Access Method
|
|
65
|
+
"""
|
|
66
|
+
data = await casbin_service.create_policy(p=p)
|
|
67
|
+
return response_base.success(request=request, data=data)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@router.post(
|
|
71
|
+
'/policies',
|
|
72
|
+
summary='Add multiple P permission policies',
|
|
73
|
+
dependencies=[
|
|
74
|
+
DependsRBAC,
|
|
75
|
+
],
|
|
76
|
+
)
|
|
77
|
+
async def create_policies(request: Request, ps: list[CreatePolicyParam]) -> ResponseModel:
|
|
78
|
+
data = await casbin_service.create_policies(ps=ps)
|
|
79
|
+
return response_base.success(request=request, data=data)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@router.put(
|
|
83
|
+
'/policy',
|
|
84
|
+
summary='Update P permission policy',
|
|
85
|
+
dependencies=[
|
|
86
|
+
DependsRBAC,
|
|
87
|
+
],
|
|
88
|
+
)
|
|
89
|
+
async def update_policy(request: Request, old: UpdatePolicyParam, new: UpdatePolicyParam) -> ResponseModel:
|
|
90
|
+
data = await casbin_service.update_policy(old=old, new=new)
|
|
91
|
+
return response_base.success(request=request, data=data)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@router.put(
|
|
95
|
+
'/policies',
|
|
96
|
+
summary='Update multiple P permission policies',
|
|
97
|
+
dependencies=[
|
|
98
|
+
DependsRBAC,
|
|
99
|
+
],
|
|
100
|
+
)
|
|
101
|
+
async def update_policies(request: Request, old: list[UpdatePolicyParam], new: list[UpdatePolicyParam]) -> ResponseModel:
|
|
102
|
+
data = await casbin_service.update_policies(old=old, new=new)
|
|
103
|
+
return response_base.success(request=request, data=data)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@router.delete(
|
|
107
|
+
'/policy',
|
|
108
|
+
summary='Delete P permission policy',
|
|
109
|
+
dependencies=[
|
|
110
|
+
DependsRBAC,
|
|
111
|
+
],
|
|
112
|
+
)
|
|
113
|
+
async def delete_policy(request: Request, p: DeletePolicyParam) -> ResponseModel:
|
|
114
|
+
data = await casbin_service.delete_policy(p=p)
|
|
115
|
+
return response_base.success(request=request, data=data)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@router.delete(
|
|
119
|
+
'/policies',
|
|
120
|
+
summary='Delete multiple P permission policies',
|
|
121
|
+
dependencies=[
|
|
122
|
+
DependsRBAC,
|
|
123
|
+
],
|
|
124
|
+
)
|
|
125
|
+
async def delete_policies(request: Request, ps: list[DeletePolicyParam]) -> ResponseModel:
|
|
126
|
+
data = await casbin_service.delete_policies(ps=ps)
|
|
127
|
+
return response_base.success(request=request, data=data)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@router.delete(
|
|
131
|
+
'/policies/all',
|
|
132
|
+
summary='Delete all P permission policies',
|
|
133
|
+
dependencies=[
|
|
134
|
+
DependsRBAC,
|
|
135
|
+
],
|
|
136
|
+
)
|
|
137
|
+
async def delete_all_policies(request: Request, sub: DeleteAllPoliciesParam) -> ResponseModel:
|
|
138
|
+
count = await casbin_service.delete_all_policies(sub=sub)
|
|
139
|
+
if count > 0:
|
|
140
|
+
return response_base.success(request=request )
|
|
141
|
+
return response_base.fail(request=request)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@router.get('/groups', summary='Get all G permission policies', dependencies=[DependsJwtAuth])
|
|
145
|
+
async def get_all_groups(request: Request) -> ResponseModel:
|
|
146
|
+
data = await casbin_service.get_group_list()
|
|
147
|
+
return response_base.success(request=request, data=data)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@router.post(
|
|
151
|
+
'/group',
|
|
152
|
+
summary='Add G permission policy',
|
|
153
|
+
dependencies=[
|
|
154
|
+
DependsRBAC,
|
|
155
|
+
],
|
|
156
|
+
)
|
|
157
|
+
async def create_group(request: Request, g: CreateUserRoleParam) -> ResponseModel:
|
|
158
|
+
"""
|
|
159
|
+
G Policy (**Depends on P policy**):
|
|
160
|
+
|
|
161
|
+
- If you add role-based access control in the P policy, you also need to add group-based access control in the G policy to actually have access rights.<br>
|
|
162
|
+
**Format**: User UUID + Role
|
|
163
|
+
|
|
164
|
+
- If you add user-based access control in the P policy, there is no need to add the corresponding G policy to have access rights.<br>
|
|
165
|
+
However, you will not have all the permissions of the user role, but only the specific access rights added by the corresponding P policy.
|
|
166
|
+
"""
|
|
167
|
+
data = await casbin_service.create_group(g=g)
|
|
168
|
+
return response_base.success(request=request, data=data)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@router.post(
|
|
172
|
+
'/groups',
|
|
173
|
+
summary='Add multiple G permission policies',
|
|
174
|
+
dependencies=[
|
|
175
|
+
DependsRBAC,
|
|
176
|
+
],
|
|
177
|
+
)
|
|
178
|
+
async def create_groups(request: Request, gs: list[CreateUserRoleParam]) -> ResponseModel:
|
|
179
|
+
data = await casbin_service.create_groups(gs=gs)
|
|
180
|
+
return response_base.success(request=request, data=data)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@router.delete(
|
|
184
|
+
'/group',
|
|
185
|
+
summary='Delete G permission policy',
|
|
186
|
+
dependencies=[
|
|
187
|
+
DependsRBAC,
|
|
188
|
+
],
|
|
189
|
+
)
|
|
190
|
+
async def delete_group(request: Request, g: DeleteUserRoleParam) -> ResponseModel:
|
|
191
|
+
data = await casbin_service.delete_group(g=g)
|
|
192
|
+
return response_base.success(request=request, data=data)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@router.delete(
|
|
196
|
+
'/groups',
|
|
197
|
+
summary='Delete multiple G permission policies',
|
|
198
|
+
dependencies=[
|
|
199
|
+
DependsRBAC,
|
|
200
|
+
],
|
|
201
|
+
)
|
|
202
|
+
async def delete_groups(request: Request, gs: list[DeleteUserRoleParam]) -> ResponseModel:
|
|
203
|
+
data = await casbin_service.delete_groups(gs=gs)
|
|
204
|
+
return response_base.success(request=request, data=data)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@router.delete(
|
|
208
|
+
'/groups/all',
|
|
209
|
+
summary='Delete all G permission policies',
|
|
210
|
+
dependencies=[
|
|
211
|
+
DependsRBAC,
|
|
212
|
+
],
|
|
213
|
+
)
|
|
214
|
+
async def delete_all_groups(request: Request, uuid: Annotated[UUID, Query(...)]) -> ResponseModel:
|
|
215
|
+
count = await casbin_service.delete_all_groups(uuid=uuid)
|
|
216
|
+
if count > 0:
|
|
217
|
+
return response_base.success(request=request)
|
|
218
|
+
return response_base.fail(request=request)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from typing import Annotated
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, Query, Request
|
|
4
|
+
|
|
5
|
+
from backend.app.admin.schema.login_log import GetLoginLogListDetails
|
|
6
|
+
from backend.app.admin.service.login_log_service import login_log_service
|
|
7
|
+
from backend.common.pagination import DependsPagination, paging_data
|
|
8
|
+
from backend.common.response.response_schema import ResponseModel, response_base
|
|
9
|
+
from backend.common.security.jwt import DependsJwtAuth
|
|
10
|
+
from backend.common.security.rbac import DependsRBAC
|
|
11
|
+
from backend.database.db_postgres import CurrentSession
|
|
12
|
+
|
|
13
|
+
router = APIRouter(prefix="/login_log", tags=["Login log"] )
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@router.get(
|
|
17
|
+
'/',
|
|
18
|
+
summary='(Fuzzy Criteria) Paging for Login Logs',
|
|
19
|
+
dependencies=[
|
|
20
|
+
DependsJwtAuth,
|
|
21
|
+
DependsPagination,
|
|
22
|
+
],
|
|
23
|
+
)
|
|
24
|
+
async def get_pagination_login_logs(
|
|
25
|
+
request: Request,
|
|
26
|
+
db: CurrentSession,
|
|
27
|
+
username: Annotated[str | None, Query()] = None,
|
|
28
|
+
status: Annotated[int | None, Query()] = None,
|
|
29
|
+
ip: Annotated[str | None, Query()] = None,
|
|
30
|
+
) -> ResponseModel:
|
|
31
|
+
log_select = await login_log_service.get_select(username=username, status=status, ip=ip)
|
|
32
|
+
page_data = await paging_data(db, log_select, GetLoginLogListDetails)
|
|
33
|
+
return response_base.success(request=request, data=page_data)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@router.delete(
|
|
37
|
+
'/',
|
|
38
|
+
summary='(Batch) Delete Login Logs',
|
|
39
|
+
dependencies=[
|
|
40
|
+
DependsRBAC,
|
|
41
|
+
DependsJwtAuth,
|
|
42
|
+
|
|
43
|
+
],
|
|
44
|
+
)
|
|
45
|
+
async def delete_login_log(request: Request, pk: Annotated[list[int], Query(...)]) -> ResponseModel:
|
|
46
|
+
count = await login_log_service.delete(pk=pk)
|
|
47
|
+
if count > 0:
|
|
48
|
+
return response_base.success(request=request)
|
|
49
|
+
return response_base.fail(request=request)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@router.delete(
|
|
53
|
+
'/all',
|
|
54
|
+
summary='Empty login log',
|
|
55
|
+
dependencies=[
|
|
56
|
+
DependsRBAC,
|
|
57
|
+
],
|
|
58
|
+
)
|
|
59
|
+
async def delete_all_login_logs(request: Request) -> ResponseModel:
|
|
60
|
+
count = await login_log_service.delete_all()
|
|
61
|
+
if count > 0:
|
|
62
|
+
return response_base.success(request=request)
|
|
63
|
+
return response_base.fail(request=request)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from typing import Annotated
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, Query, Request
|
|
4
|
+
|
|
5
|
+
from backend.app.admin.schema.opera_log import GetOperaLogListDetails
|
|
6
|
+
from backend.app.admin.service.opera_log_service import opera_log_service
|
|
7
|
+
from backend.common.pagination import DependsPagination, paging_data
|
|
8
|
+
from backend.common.response.response_schema import ResponseModel, response_base
|
|
9
|
+
from backend.common.security.jwt import DependsJwtAuth
|
|
10
|
+
from backend.common.security.rbac import DependsRBAC
|
|
11
|
+
from backend.database.db_postgres import CurrentSession
|
|
12
|
+
|
|
13
|
+
router = APIRouter(prefix="/opera_log", tags=["Operation Log"])
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@router.get(
|
|
17
|
+
'/',
|
|
18
|
+
summary='(Fuzzy Condition) Paging for Operation Logs',
|
|
19
|
+
dependencies=[
|
|
20
|
+
DependsJwtAuth,
|
|
21
|
+
DependsPagination,
|
|
22
|
+
],
|
|
23
|
+
)
|
|
24
|
+
async def get_pagination_opera_logs(
|
|
25
|
+
request: Request,
|
|
26
|
+
db: CurrentSession,
|
|
27
|
+
username: Annotated[str | None, Query()] = None,
|
|
28
|
+
status: Annotated[int | None, Query()] = None,
|
|
29
|
+
ip: Annotated[str | None, Query()] = None,
|
|
30
|
+
) -> ResponseModel:
|
|
31
|
+
log_select = await opera_log_service.get_select(username=username, status=status, ip=ip)
|
|
32
|
+
page_data = await paging_data(db, log_select, GetOperaLogListDetails)
|
|
33
|
+
return response_base.success(request=request, data=page_data)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@router.delete(
|
|
37
|
+
'/',
|
|
38
|
+
summary='Delete (batch) operation logs',
|
|
39
|
+
dependencies=[
|
|
40
|
+
DependsRBAC,
|
|
41
|
+
],
|
|
42
|
+
)
|
|
43
|
+
async def delete_opera_log(request: Request, pk: Annotated[list[int], Query(...)]) -> ResponseModel:
|
|
44
|
+
count = await opera_log_service.delete(pk=pk)
|
|
45
|
+
if count > 0:
|
|
46
|
+
return response_base.success(request=request)
|
|
47
|
+
return response_base.fail(request=request)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@router.delete(
|
|
51
|
+
'/all',
|
|
52
|
+
summary='Delete (batch) operation log',
|
|
53
|
+
dependencies=[
|
|
54
|
+
DependsRBAC,
|
|
55
|
+
],
|
|
56
|
+
)
|
|
57
|
+
async def delete_all_opera_logs(request: Request) -> ResponseModel:
|
|
58
|
+
count = await opera_log_service.delete_all()
|
|
59
|
+
if count > 0:
|
|
60
|
+
return response_base.success(request=request)
|
|
61
|
+
return response_base.fail(request=request)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from typing import Annotated
|
|
2
|
+
from fastapi import APIRouter, Path, Query, Request
|
|
3
|
+
from backend.common.pagination import DependsPagination, paging_data
|
|
4
|
+
from backend.database.db_postgres import CurrentSession
|
|
5
|
+
from backend.utils.serializers import select_as_dict, select_list_serialize
|
|
6
|
+
|
|
7
|
+
from backend.app.admin.schema.role import GetRoleListDetails, UpdateRoleParam, CreateRoleParam
|
|
8
|
+
from backend.app.admin.service.role_service import role_service
|
|
9
|
+
from backend.common.response.response_schema import ResponseModel, response_base
|
|
10
|
+
from backend.common.security.jwt import DependsJwtAuth
|
|
11
|
+
|
|
12
|
+
router = APIRouter(prefix="/role", tags=["Role"])
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@router.get("/all", summary="Get all roles", dependencies=[DependsJwtAuth])
|
|
16
|
+
async def get_all_roles(request: Request,) -> ResponseModel:
|
|
17
|
+
roles = await role_service.get_all()
|
|
18
|
+
data = select_list_serialize(roles)
|
|
19
|
+
return response_base.success(request=request, data=data)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@router.get("/{pk}/all", summary="Get all user roles", dependencies=[DependsJwtAuth])
|
|
23
|
+
async def get_user_all_roles(request: Request, pk: Annotated[int, Path(...)]) -> ResponseModel:
|
|
24
|
+
roles = await role_service.get_user_roles(pk=pk)
|
|
25
|
+
data = select_list_serialize(roles)
|
|
26
|
+
return response_base.success(request=request, data=data)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@router.get(
|
|
30
|
+
"/{pk}",
|
|
31
|
+
summary="Get role by ID",
|
|
32
|
+
dependencies=[DependsJwtAuth]
|
|
33
|
+
)
|
|
34
|
+
async def get_role(request: Request, pk: Annotated[int, Path(...)]) -> ResponseModel:
|
|
35
|
+
role = await role_service.get_by_id(role_id=pk)
|
|
36
|
+
data = GetRoleListDetails(**select_as_dict(role))
|
|
37
|
+
return response_base.success(request=request, data=data)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@router.get(
|
|
41
|
+
"/",
|
|
42
|
+
summary="Get roles pagination",
|
|
43
|
+
dependencies=[
|
|
44
|
+
DependsJwtAuth,
|
|
45
|
+
DependsPagination,
|
|
46
|
+
],
|
|
47
|
+
)
|
|
48
|
+
async def get_pagination_roles(
|
|
49
|
+
request: Request,
|
|
50
|
+
db: CurrentSession,
|
|
51
|
+
name: Annotated[str | None, Query()] = None,
|
|
52
|
+
data_scope: Annotated[int | None, Query()] = None,
|
|
53
|
+
status: Annotated[int | None, Query()] = None,
|
|
54
|
+
) -> ResponseModel:
|
|
55
|
+
role_select = await role_service.get_select(
|
|
56
|
+
name=name, data_scope=data_scope, status=status
|
|
57
|
+
)
|
|
58
|
+
page_data = await paging_data(db, role_select, GetRoleListDetails)
|
|
59
|
+
return response_base.success(request=request, data=page_data)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@router.post(
|
|
63
|
+
"/",
|
|
64
|
+
summary="Role creation",
|
|
65
|
+
dependencies=[
|
|
66
|
+
# Depends(RequestPermission('sys:role:add')),
|
|
67
|
+
# DependsRBAC,
|
|
68
|
+
DependsJwtAuth,
|
|
69
|
+
],
|
|
70
|
+
)
|
|
71
|
+
async def create_role(request: Request, obj: CreateRoleParam) -> ResponseModel:
|
|
72
|
+
await role_service.create(obj=obj)
|
|
73
|
+
return response_base.success(request=request)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@router.put(
|
|
77
|
+
"/{pk}",
|
|
78
|
+
summary="Update role",
|
|
79
|
+
dependencies=[
|
|
80
|
+
# Depends(RequestPermission('sys:role:edit')),
|
|
81
|
+
# DependsRBAC,
|
|
82
|
+
DependsJwtAuth,
|
|
83
|
+
],
|
|
84
|
+
)
|
|
85
|
+
async def update_role(
|
|
86
|
+
request: Request,
|
|
87
|
+
pk: Annotated[int, Path(...)], obj: UpdateRoleParam
|
|
88
|
+
) -> ResponseModel:
|
|
89
|
+
count = await role_service.update(pk=pk, obj=obj)
|
|
90
|
+
if count > 0:
|
|
91
|
+
return response_base.success(request=request)
|
|
92
|
+
return response_base.fail(request=request)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@router.delete(
|
|
96
|
+
"/",
|
|
97
|
+
summary="Delete roles",
|
|
98
|
+
dependencies=[
|
|
99
|
+
# Depends(RequestPermission('sys:role:del')),
|
|
100
|
+
# DependsRBAC,
|
|
101
|
+
DependsJwtAuth,
|
|
102
|
+
],
|
|
103
|
+
)
|
|
104
|
+
async def delete_role(request: Request, pk: Annotated[list[int], Query(...)]) -> ResponseModel:
|
|
105
|
+
count = await role_service.delete(pk=pk)
|
|
106
|
+
if count > 0:
|
|
107
|
+
return response_base.success(request=request)
|
|
108
|
+
return response_base.fail(request=request)
|