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,135 @@
|
|
|
1
|
+
from uuid import UUID
|
|
2
|
+
|
|
3
|
+
from sqlalchemy import Select
|
|
4
|
+
|
|
5
|
+
from backend.crud.crud_casbin import casbin_dao
|
|
6
|
+
from backend.app.admin.schema.casbin_rule import (
|
|
7
|
+
CreatePolicyParam,
|
|
8
|
+
CreateUserRoleParam,
|
|
9
|
+
DeleteAllPoliciesParam,
|
|
10
|
+
DeletePolicyParam,
|
|
11
|
+
DeleteUserRoleParam,
|
|
12
|
+
UpdatePolicyParam,
|
|
13
|
+
)
|
|
14
|
+
from backend.common.exception import errors
|
|
15
|
+
from backend.common.security.rbac import rbac
|
|
16
|
+
from backend.database.db_postgres import async_db_session
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CasbinService:
|
|
20
|
+
@staticmethod
|
|
21
|
+
async def get_casbin_list(*, ptype: str, sub: str) -> Select:
|
|
22
|
+
return await casbin_dao.get_list(ptype, sub)
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
async def get_policy_list(*, role: int | None = None) -> list:
|
|
26
|
+
enforcer = await rbac.enforcer()
|
|
27
|
+
if role is not None:
|
|
28
|
+
data = enforcer.get_filtered_named_policy('p', 0, str(role))
|
|
29
|
+
else:
|
|
30
|
+
data = enforcer.get_policy()
|
|
31
|
+
return data
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
async def create_policy(*, p: CreatePolicyParam) -> bool:
|
|
35
|
+
enforcer = await rbac.enforcer()
|
|
36
|
+
data = await enforcer.add_policy(p.sub, p.path, p.method)
|
|
37
|
+
if not data:
|
|
38
|
+
raise errors.ForbiddenError(msg='Permission already exists')
|
|
39
|
+
return data
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
async def create_policies(*, ps: list[CreatePolicyParam]) -> bool:
|
|
43
|
+
enforcer = await rbac.enforcer()
|
|
44
|
+
data = await enforcer.add_policies([list(p.model_dump().values()) for p in ps])
|
|
45
|
+
if not data:
|
|
46
|
+
raise errors.ForbiddenError(msg='Permission already exists')
|
|
47
|
+
return data
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
async def update_policy(*, old: UpdatePolicyParam, new: UpdatePolicyParam) -> bool:
|
|
51
|
+
enforcer = await rbac.enforcer()
|
|
52
|
+
_p = enforcer.has_policy(old.sub, old.path, old.method)
|
|
53
|
+
if not _p:
|
|
54
|
+
raise errors.NotFoundError(msg='Permission does not exist')
|
|
55
|
+
data = await enforcer.update_policy([old.sub, old.path, old.method], [new.sub, new.path, new.method])
|
|
56
|
+
return data
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
async def update_policies(*, old: list[UpdatePolicyParam], new: list[UpdatePolicyParam]) -> bool:
|
|
60
|
+
enforcer = await rbac.enforcer()
|
|
61
|
+
data = await enforcer.update_policies(
|
|
62
|
+
[list(o.model_dump().values()) for o in old], [list(n.model_dump().values()) for n in new]
|
|
63
|
+
)
|
|
64
|
+
return data
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
async def delete_policy(*, p: DeletePolicyParam) -> bool:
|
|
68
|
+
enforcer = await rbac.enforcer()
|
|
69
|
+
_p = enforcer.has_policy(p.sub, p.path, p.method)
|
|
70
|
+
if not _p:
|
|
71
|
+
raise errors.NotFoundError(msg='Permission does not exist')
|
|
72
|
+
data = await enforcer.remove_policy(p.sub, p.path, p.method)
|
|
73
|
+
return data
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
async def delete_policies(*, ps: list[DeletePolicyParam]) -> bool:
|
|
77
|
+
enforcer = await rbac.enforcer()
|
|
78
|
+
data = await enforcer.remove_policies([list(p.model_dump().values()) for p in ps])
|
|
79
|
+
if not data:
|
|
80
|
+
raise errors.NotFoundError(msg='Permission does not exist')
|
|
81
|
+
return data
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
async def delete_all_policies(*, sub: DeleteAllPoliciesParam) -> int:
|
|
85
|
+
async with async_db_session.begin() as db:
|
|
86
|
+
count = await casbin_dao.delete_policies_by_sub(db, sub)
|
|
87
|
+
return count
|
|
88
|
+
|
|
89
|
+
@staticmethod
|
|
90
|
+
async def get_group_list() -> list:
|
|
91
|
+
enforcer = await rbac.enforcer()
|
|
92
|
+
data = enforcer.get_grouping_policy()
|
|
93
|
+
return data
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
async def create_group(*, g: CreateUserRoleParam) -> bool:
|
|
97
|
+
enforcer = await rbac.enforcer()
|
|
98
|
+
data = await enforcer.add_grouping_policy(g.uuid, g.role)
|
|
99
|
+
if not data:
|
|
100
|
+
raise errors.ForbiddenError(msg='Permission already exists')
|
|
101
|
+
return data
|
|
102
|
+
|
|
103
|
+
@staticmethod
|
|
104
|
+
async def create_groups(*, gs: list[CreateUserRoleParam]) -> bool:
|
|
105
|
+
enforcer = await rbac.enforcer()
|
|
106
|
+
data = await enforcer.add_grouping_policies([list(g.model_dump().values()) for g in gs])
|
|
107
|
+
if not data:
|
|
108
|
+
raise errors.ForbiddenError(msg='Permission already exists')
|
|
109
|
+
return data
|
|
110
|
+
|
|
111
|
+
@staticmethod
|
|
112
|
+
async def delete_group(*, g: DeleteUserRoleParam) -> bool:
|
|
113
|
+
enforcer = await rbac.enforcer()
|
|
114
|
+
_g = enforcer.has_grouping_policy(g.uuid, g.role)
|
|
115
|
+
if not _g:
|
|
116
|
+
raise errors.NotFoundError(msg='Permission does not exist')
|
|
117
|
+
data = await enforcer.remove_grouping_policy(g.uuid, g.role)
|
|
118
|
+
return data
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
async def delete_groups(*, gs: list[DeleteUserRoleParam]) -> bool:
|
|
122
|
+
enforcer = await rbac.enforcer()
|
|
123
|
+
data = await enforcer.remove_grouping_policies([list(g.model_dump().values()) for g in gs])
|
|
124
|
+
if not data:
|
|
125
|
+
raise errors.NotFoundError(msg='Permission does not exist')
|
|
126
|
+
return data
|
|
127
|
+
|
|
128
|
+
@staticmethod
|
|
129
|
+
async def delete_all_groups(*, uuid: UUID) -> int:
|
|
130
|
+
async with async_db_session.begin() as db:
|
|
131
|
+
count = await casbin_dao.delete_groups_by_uuid(db, uuid)
|
|
132
|
+
return count
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
casbin_service = CasbinService()
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
from fastapi import Request
|
|
4
|
+
from sqlalchemy import Select
|
|
5
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
6
|
+
|
|
7
|
+
from backend.crud.crud_login_log import login_log_dao
|
|
8
|
+
from backend.app.admin.schema.login_log import CreateLoginLogParam
|
|
9
|
+
from backend.common.log import log
|
|
10
|
+
from backend.database.db_postgres import async_db_session
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LoginLogService:
|
|
14
|
+
@staticmethod
|
|
15
|
+
async def get_select(*, username: str, status: int, ip: str) -> Select:
|
|
16
|
+
return await login_log_dao.get_list(username=username, status=status, ip=ip)
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
async def create(
|
|
20
|
+
*,
|
|
21
|
+
db: AsyncSession,
|
|
22
|
+
request: Request,
|
|
23
|
+
user_x_id: str,
|
|
24
|
+
email: str,
|
|
25
|
+
login_time: datetime,
|
|
26
|
+
status: int,
|
|
27
|
+
msg: str,
|
|
28
|
+
) -> None:
|
|
29
|
+
try:
|
|
30
|
+
obj_in = CreateLoginLogParam(
|
|
31
|
+
user_x_id=user_x_id,
|
|
32
|
+
email=email,
|
|
33
|
+
status=status,
|
|
34
|
+
ip=request.state.ip,
|
|
35
|
+
country=request.state.country,
|
|
36
|
+
region=request.state.region,
|
|
37
|
+
city=request.state.city,
|
|
38
|
+
user_agent=request.state.user_agent,
|
|
39
|
+
browser=request.state.browser,
|
|
40
|
+
os=request.state.os,
|
|
41
|
+
device=request.state.device,
|
|
42
|
+
msg=msg,
|
|
43
|
+
login_time=login_time,
|
|
44
|
+
)
|
|
45
|
+
await login_log_dao.create(db, obj_in)
|
|
46
|
+
except Exception as e:
|
|
47
|
+
log.error(f'Login log creation failure: {e}')
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
async def delete(*, pk: list[int]) -> int:
|
|
51
|
+
async with async_db_session.begin() as db:
|
|
52
|
+
count = await login_log_dao.delete(db, pk)
|
|
53
|
+
return count
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
async def delete_all() -> int:
|
|
57
|
+
async with async_db_session.begin() as db:
|
|
58
|
+
count = await login_log_dao.delete_all(db)
|
|
59
|
+
return count
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
login_log_service = LoginLogService()
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from sqlalchemy import Select
|
|
2
|
+
|
|
3
|
+
from backend.crud.crud_opera_log import opera_log_dao
|
|
4
|
+
from backend.app.admin.schema.opera_log import CreateOperaLogParam
|
|
5
|
+
from backend.database.db_postgres import async_db_session
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class OperaLogService:
|
|
9
|
+
@staticmethod
|
|
10
|
+
async def get_select(*, username: str | None = None, status: int | None = None, ip: str | None = None) -> Select:
|
|
11
|
+
return await opera_log_dao.get_list(username=username, status=status, ip=ip)
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
async def create(*, obj_in: CreateOperaLogParam):
|
|
15
|
+
async with async_db_session.begin() as db:
|
|
16
|
+
await opera_log_dao.create(db, obj_in)
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
async def delete(*, pk: list[int]) -> int:
|
|
20
|
+
async with async_db_session.begin() as db:
|
|
21
|
+
count = await opera_log_dao.delete(db, pk)
|
|
22
|
+
return count
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
async def delete_all() -> int:
|
|
26
|
+
async with async_db_session.begin() as db:
|
|
27
|
+
count = await opera_log_dao.delete_all(db)
|
|
28
|
+
return count
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
opera_log_service = OperaLogService()
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from typing import Sequence
|
|
2
|
+
|
|
3
|
+
from fastapi import Request
|
|
4
|
+
from sqlalchemy import Select
|
|
5
|
+
|
|
6
|
+
from backend.crud.crud_role import role_dao
|
|
7
|
+
from backend.models import Role
|
|
8
|
+
from backend.app.admin.schema.role import CreateRoleParam, UpdateRoleMenuParam, UpdateRoleParam
|
|
9
|
+
from backend.common.exception import errors
|
|
10
|
+
from backend.core.conf import settings
|
|
11
|
+
from backend.database.db_postgres import async_db_session
|
|
12
|
+
from backend.database.db_redis import redis_client
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RoleService:
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
async def get_by_id(role_id: int) -> Role | None:
|
|
19
|
+
"""
|
|
20
|
+
Get role by id
|
|
21
|
+
|
|
22
|
+
:param db:
|
|
23
|
+
:param role_id:
|
|
24
|
+
:return:
|
|
25
|
+
"""
|
|
26
|
+
async with async_db_session() as db:
|
|
27
|
+
return await role_dao.get(db, role_id)
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
async def get_all() -> Sequence[Role]:
|
|
31
|
+
async with async_db_session() as db:
|
|
32
|
+
roles = await role_dao.get_all(db)
|
|
33
|
+
return roles
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
async def get_user_roles(*, pk: int) -> Sequence[Role]:
|
|
37
|
+
async with async_db_session() as db:
|
|
38
|
+
roles = await role_dao.get_user_roles(db, user_id=pk)
|
|
39
|
+
return roles
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
async def get_select(*, name: str = None, data_scope: int = None, status: int = None) -> Select:
|
|
43
|
+
return await role_dao.get_list(name=name, data_scope=data_scope, status=status)
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
async def create(*, obj: CreateRoleParam) -> None:
|
|
47
|
+
async with async_db_session.begin() as db:
|
|
48
|
+
role = await role_dao.get_by_name(db, obj.name)
|
|
49
|
+
if role:
|
|
50
|
+
raise errors.ForbiddenError(msg='already exists')
|
|
51
|
+
await role_dao.create(db, obj)
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
async def get_by_name(name: str) -> Role | None:
|
|
55
|
+
async with async_db_session() as db:
|
|
56
|
+
return await role_dao.get_by_name(db, name)
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
async def update(*, pk: int, obj: UpdateRoleParam) -> int:
|
|
60
|
+
async with async_db_session.begin() as db:
|
|
61
|
+
role = await role_dao.get(db, pk)
|
|
62
|
+
if not role:
|
|
63
|
+
raise errors.NotFoundError(msg='not found')
|
|
64
|
+
if role.name != obj.name:
|
|
65
|
+
role = await role_dao.get_by_name(db, obj.name)
|
|
66
|
+
if role:
|
|
67
|
+
raise errors.ForbiddenError(msg='already exists')
|
|
68
|
+
count = await role_dao.update_roleinfo(db, pk, obj)
|
|
69
|
+
return count
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
async def delete(*, pk: list[int]) -> int:
|
|
74
|
+
async with async_db_session.begin() as db:
|
|
75
|
+
count = await role_dao.delete(db, pk)
|
|
76
|
+
return count
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
role_service = RoleService()
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from datetime import datetime, timedelta
|
|
2
|
+
from backend.common.enums import Secure_token_type
|
|
3
|
+
from backend.common.exception import errors
|
|
4
|
+
from backend.common.security.sec_token import generate_secret_token
|
|
5
|
+
from backend.app.admin.schema.token import Secure_token
|
|
6
|
+
from backend.database.db_redis import redis_client
|
|
7
|
+
from backend.core.conf import settings
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Secure_tokenService:
|
|
13
|
+
@staticmethod
|
|
14
|
+
async def gen_token(user_x_id: str, type: Secure_token_type, expire: int | None = None) -> str:
|
|
15
|
+
token = generate_secret_token()
|
|
16
|
+
expiration=(datetime.now() + timedelta(minutes=expire)) if expire is not None else (datetime.now() + timedelta(minutes=5))
|
|
17
|
+
password_reset_token = Secure_token(
|
|
18
|
+
token=token,
|
|
19
|
+
token_type=type,
|
|
20
|
+
user_x_id=user_x_id,
|
|
21
|
+
expiration=expiration,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
add = await redis_client.set(
|
|
25
|
+
f"{settings.USER_SECURE_TOKEN_REDIS_PREFIX}:{user_x_id}:{type.value}",
|
|
26
|
+
password_reset_token.model_dump_json(),
|
|
27
|
+
timedelta(minutes=expire) if expire is not None else None,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if not add:
|
|
31
|
+
raise errors.ServerError(msg='Token generation failed')
|
|
32
|
+
return token
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
async def check_token(token: str, user_x_id: str, type: Secure_token_type) -> bool:
|
|
36
|
+
rst_token = await redis_client.get(f"{settings.USER_SECURE_TOKEN_REDIS_PREFIX}:{user_x_id}:{type.value}")
|
|
37
|
+
if not rst_token:
|
|
38
|
+
raise errors.TokenError(msg='Token has expired')
|
|
39
|
+
|
|
40
|
+
validate_token = Secure_token.model_validate_json(rst_token)
|
|
41
|
+
|
|
42
|
+
if validate_token.used:
|
|
43
|
+
raise errors.TokenError(msg='Token has been used')
|
|
44
|
+
|
|
45
|
+
if validate_token.expiration < datetime.now():
|
|
46
|
+
raise errors.TokenError(msg='Token has expired')
|
|
47
|
+
if validate_token.token_type != type.value:
|
|
48
|
+
raise errors.TokenError(msg='Token type is invalid')
|
|
49
|
+
if validate_token.token != token:
|
|
50
|
+
raise errors.TokenError(msg='Token user is invalid')
|
|
51
|
+
|
|
52
|
+
validate_token.used = True
|
|
53
|
+
await redis_client.set(
|
|
54
|
+
f"{settings.USER_SECURE_TOKEN_REDIS_PREFIX}:{user_x_id}:{type.value}",
|
|
55
|
+
validate_token.model_dump_json(),
|
|
56
|
+
)
|
|
57
|
+
print("validate_token =====>", validate_token.used)
|
|
58
|
+
return True
|
|
59
|
+
|
|
60
|
+
secure_token_service = Secure_tokenService()
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
from fastapi import Request
|
|
5
|
+
from pydantic import EmailStr
|
|
6
|
+
from sqlalchemy import Select, Sequence
|
|
7
|
+
|
|
8
|
+
from backend.app.admin.service.secure_token_service import secure_token_service
|
|
9
|
+
from backend.common.enums import Secure_token_type
|
|
10
|
+
from backend.crud.crud_user import user_dao
|
|
11
|
+
from backend.models import User
|
|
12
|
+
from backend.app.admin.schema.user import (
|
|
13
|
+
UpdatePasswordParam,
|
|
14
|
+
UserResetPassword,
|
|
15
|
+
UpdateUserParam
|
|
16
|
+
)
|
|
17
|
+
from backend.common.exception import errors
|
|
18
|
+
from backend.common.security.jwt import get_hash_password, password_verify, superuser_verify
|
|
19
|
+
from backend.core.conf import settings
|
|
20
|
+
from backend.database.db_postgres import async_db_session
|
|
21
|
+
from backend.database.db_redis import redis_client
|
|
22
|
+
from backend.common.enums import Role as Role_enum
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ClientService:
|
|
27
|
+
@staticmethod
|
|
28
|
+
async def pwd_update(*, request: Request, obj: UpdatePasswordParam) -> int:
|
|
29
|
+
async with async_db_session.begin() as db:
|
|
30
|
+
user = await user_dao.get(db, request.user.id)
|
|
31
|
+
if not password_verify(f'{obj.old_password}{user.salt}', user.password):
|
|
32
|
+
raise errors.ForbiddenError(msg='Original password is wrong')
|
|
33
|
+
|
|
34
|
+
np1 = obj.new_password
|
|
35
|
+
np2 = obj.confirm_password
|
|
36
|
+
|
|
37
|
+
if np1 != np2:
|
|
38
|
+
raise errors.ForbiddenError(msg='Inconsistent password entry')
|
|
39
|
+
|
|
40
|
+
new_pwd = get_hash_password(f'{obj.new_password}{user.salt}')
|
|
41
|
+
count = await user_dao.reset_password(db, request.user.id, new_pwd)
|
|
42
|
+
key_prefix = [
|
|
43
|
+
f'{settings.TOKEN_REDIS_PREFIX}:{request.user.id}',
|
|
44
|
+
f'{settings.TOKEN_REFRESH_REDIS_PREFIX}:{request.user.id}',
|
|
45
|
+
f'{settings.JWT_USER_REDIS_PREFIX}:{request.user.id}',
|
|
46
|
+
]
|
|
47
|
+
for key in key_prefix:
|
|
48
|
+
await redis_client.delete_prefix(key)
|
|
49
|
+
return count
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
async def pwd_reset(*, email: EmailStr, token: str, obj: UserResetPassword) -> int:
|
|
53
|
+
async with async_db_session.begin() as db:
|
|
54
|
+
user = await user_dao.get_by_email(db, email)
|
|
55
|
+
if not user:
|
|
56
|
+
raise errors.NotFoundError(msg='Invalid information')
|
|
57
|
+
np1 = obj.new_password
|
|
58
|
+
np2 = obj.confirm_password
|
|
59
|
+
|
|
60
|
+
if np1 != np2:
|
|
61
|
+
raise errors.ForbiddenError(msg='Inconsistent password entry')
|
|
62
|
+
check_token = await secure_token_service.check_token(token=token, user_x_id=user.x_id, type=Secure_token_type.RESET_PWD)
|
|
63
|
+
if not check_token:
|
|
64
|
+
raise errors.ForbiddenError(msg='Token is invalid')
|
|
65
|
+
|
|
66
|
+
new_pwd = get_hash_password(f'{obj.new_password}{user.salt}')
|
|
67
|
+
count = await user_dao.reset_password(db, user.id, new_pwd)
|
|
68
|
+
key_prefix = [
|
|
69
|
+
f'{settings.TOKEN_REDIS_PREFIX}:{user.id}',
|
|
70
|
+
f'{settings.TOKEN_REFRESH_REDIS_PREFIX}:{user.id}',
|
|
71
|
+
f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}',
|
|
72
|
+
]
|
|
73
|
+
for key in key_prefix:
|
|
74
|
+
await redis_client.delete_prefix(key)
|
|
75
|
+
return count
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
async def get_userinfo(*, email: str) -> User:
|
|
79
|
+
async with async_db_session() as db:
|
|
80
|
+
user = await user_dao.get_with_relation(db, email=email)
|
|
81
|
+
if not user:
|
|
82
|
+
raise errors.NotFoundError(msg='The user does not exist')
|
|
83
|
+
return user
|
|
84
|
+
@staticmethod
|
|
85
|
+
async def get_by_email(*, email: EmailStr) -> User | None:
|
|
86
|
+
async with async_db_session() as db:
|
|
87
|
+
user = await user_dao.get_with_relation(db, email=email)
|
|
88
|
+
return user
|
|
89
|
+
@staticmethod
|
|
90
|
+
async def update_profile_image(*, request: Request, email: str, profile_image: dict) -> int:
|
|
91
|
+
async with async_db_session.begin() as db:
|
|
92
|
+
input_user = await user_dao.get_by_email(db, email)
|
|
93
|
+
if not input_user:
|
|
94
|
+
raise errors.NotFoundError(msg='The user does not exist')
|
|
95
|
+
count = await user_dao.update_profile_image(db, input_user.id, profile_image)
|
|
96
|
+
return count
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
async def update(*, id: int, obj: UpdateUserParam) -> int:
|
|
100
|
+
async with async_db_session.begin() as db:
|
|
101
|
+
input_user = await user_dao.get_with_relation(db, id=id, populates=[])
|
|
102
|
+
if not input_user:
|
|
103
|
+
raise errors.NotFoundError(msg='The user does not exist')
|
|
104
|
+
|
|
105
|
+
count = await user_dao.update_user_info(db, id, obj.model_dump(exclude_none=True, exclude_unset=True, exclude={}))
|
|
106
|
+
return count
|
|
107
|
+
@staticmethod
|
|
108
|
+
async def get_profile(*, id: int) -> User:
|
|
109
|
+
async with async_db_session() as db:
|
|
110
|
+
user = await user_dao.get_with_relation(db, id=id, populates=[])
|
|
111
|
+
if not user:
|
|
112
|
+
raise errors.NotFoundError(msg='The user does not exist')
|
|
113
|
+
return user
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
async def get_select(
|
|
117
|
+
*,
|
|
118
|
+
q: str | None = None,
|
|
119
|
+
email: str | None = None,
|
|
120
|
+
phone: str | None = None,
|
|
121
|
+
status: bool | None = None,
|
|
122
|
+
role: str | None = None
|
|
123
|
+
) -> Select:
|
|
124
|
+
async with async_db_session() as db:
|
|
125
|
+
return await user_dao.get_list(email=email, phone=phone, status=status)
|
|
126
|
+
|
|
127
|
+
@staticmethod
|
|
128
|
+
async def get_all() -> Sequence[User]:
|
|
129
|
+
async with async_db_session() as db:
|
|
130
|
+
users = await user_dao.get_all(db)
|
|
131
|
+
return users
|
|
132
|
+
@staticmethod
|
|
133
|
+
async def delete(*, email: str) -> int:
|
|
134
|
+
async with async_db_session.begin() as db:
|
|
135
|
+
input_user = await user_dao.get_by_email(db, email=email)
|
|
136
|
+
if not input_user:
|
|
137
|
+
raise errors.NotFoundError(msg='The user does not exist')
|
|
138
|
+
count = await user_dao.delete(db, input_user.id)
|
|
139
|
+
key_prefix = [
|
|
140
|
+
f'{settings.TOKEN_REDIS_PREFIX}:{input_user.id}',
|
|
141
|
+
f'{settings.TOKEN_REFRESH_REDIS_PREFIX}:{input_user.id}',
|
|
142
|
+
]
|
|
143
|
+
for key in key_prefix:
|
|
144
|
+
await redis_client.delete_prefix(key)
|
|
145
|
+
return count
|
|
146
|
+
|
|
147
|
+
@staticmethod
|
|
148
|
+
async def get_by_x_id(x_id: str) -> User | None:
|
|
149
|
+
async with async_db_session() as db:
|
|
150
|
+
return await user_dao.get_by_stb_id(db, x_id)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
user_service = ClientService()
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from fastapi import APIRouter
|
|
2
|
+
from backend.app import Handlers
|
|
3
|
+
from backend.core.conf import settings
|
|
4
|
+
|
|
5
|
+
admin_router = APIRouter(prefix=f"{settings.FASTAPI_API_V1_PATH}")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
for handler in Handlers.iterator():
|
|
9
|
+
if getattr(handler, 'router', None):
|
|
10
|
+
if handler.__name__.split('.')[-4] == 'admin':
|
|
11
|
+
admin_router.include_router(handler.router)
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .cloud_storage import MinioStorage
|
|
2
|
+
from backend.core.conf import settings
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
mstorage = MinioStorage(
|
|
7
|
+
access_key=settings.MINIO_ACCESS_KEY,
|
|
8
|
+
secret_key=settings.MINIO_SECRET_KEY,
|
|
9
|
+
endpoint_url=settings.MINIO_ENDPOINT,
|
|
10
|
+
bucket_name=settings.MINIO_BUCKET_NAME,
|
|
11
|
+
)
|