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.
Files changed (141) hide show
  1. shaapi/__init__.py +3 -0
  2. shaapi/cli.py +97 -0
  3. shaapi/generator.py +114 -0
  4. shaapi/template/.dockerignore +37 -0
  5. shaapi/template/.env.template +59 -0
  6. shaapi/template/.gitattributes +12 -0
  7. shaapi/template/.gitignore +170 -0
  8. shaapi/template/.gitlab-ci.yml +89 -0
  9. shaapi/template/Dockerfile +59 -0
  10. shaapi/template/LICENSE +21 -0
  11. shaapi/template/README.md +206 -0
  12. shaapi/template/backend/.gitignore +164 -0
  13. shaapi/template/backend/__init__.py +0 -0
  14. shaapi/template/backend/alembic/README +1 -0
  15. shaapi/template/backend/alembic/env.py +102 -0
  16. shaapi/template/backend/alembic/script.py.mako +26 -0
  17. shaapi/template/backend/alembic/versions/2026_06_08_1024-64524c63b666_initial.py +143 -0
  18. shaapi/template/backend/alembic.ini +117 -0
  19. shaapi/template/backend/app/__init__.py +55 -0
  20. shaapi/template/backend/app/admin/__init__.py +1 -0
  21. shaapi/template/backend/app/admin/api/v1/__init__.py +0 -0
  22. shaapi/template/backend/app/admin/api/v1/auth.py +59 -0
  23. shaapi/template/backend/app/admin/api/v1/casbin.py +218 -0
  24. shaapi/template/backend/app/admin/api/v1/login_log.py +63 -0
  25. shaapi/template/backend/app/admin/api/v1/opera_log.py +61 -0
  26. shaapi/template/backend/app/admin/api/v1/role.py +108 -0
  27. shaapi/template/backend/app/admin/api/v1/user.py +47 -0
  28. shaapi/template/backend/app/admin/schema/casbin_rule.py +45 -0
  29. shaapi/template/backend/app/admin/schema/login_log.py +36 -0
  30. shaapi/template/backend/app/admin/schema/opera_log.py +43 -0
  31. shaapi/template/backend/app/admin/schema/role.py +36 -0
  32. shaapi/template/backend/app/admin/schema/sso.py +37 -0
  33. shaapi/template/backend/app/admin/schema/token.py +74 -0
  34. shaapi/template/backend/app/admin/schema/user.py +93 -0
  35. shaapi/template/backend/app/admin/service/auth_service.py +233 -0
  36. shaapi/template/backend/app/admin/service/casbin_service.py +135 -0
  37. shaapi/template/backend/app/admin/service/login_log_service.py +62 -0
  38. shaapi/template/backend/app/admin/service/opera_log_service.py +31 -0
  39. shaapi/template/backend/app/admin/service/role_service.py +79 -0
  40. shaapi/template/backend/app/admin/service/secure_token_service.py +60 -0
  41. shaapi/template/backend/app/admin/service/user_service.py +153 -0
  42. shaapi/template/backend/app/api.py +11 -0
  43. shaapi/template/backend/common/__init__.py +0 -0
  44. shaapi/template/backend/common/cloud_storage/__init__.py +11 -0
  45. shaapi/template/backend/common/cloud_storage/cloud_storage.py +180 -0
  46. shaapi/template/backend/common/dataclasses.py +52 -0
  47. shaapi/template/backend/common/email_conf/email.py +105 -0
  48. shaapi/template/backend/common/enums.py +144 -0
  49. shaapi/template/backend/common/exception/__init__.py +0 -0
  50. shaapi/template/backend/common/exception/errors.py +87 -0
  51. shaapi/template/backend/common/exception/exception_handler.py +280 -0
  52. shaapi/template/backend/common/log.py +123 -0
  53. shaapi/template/backend/common/model.py +68 -0
  54. shaapi/template/backend/common/pagination.py +83 -0
  55. shaapi/template/backend/common/response/__init__.py +0 -0
  56. shaapi/template/backend/common/response/response_code.py +158 -0
  57. shaapi/template/backend/common/response/response_schema.py +110 -0
  58. shaapi/template/backend/common/schema.py +144 -0
  59. shaapi/template/backend/common/security/jwt.py +203 -0
  60. shaapi/template/backend/common/security/rbac.py +98 -0
  61. shaapi/template/backend/common/security/sec_token.py +6 -0
  62. shaapi/template/backend/common/socketio/action.py +11 -0
  63. shaapi/template/backend/common/socketio/server.py +50 -0
  64. shaapi/template/backend/common/sso/base.py +69 -0
  65. shaapi/template/backend/common/sso/google.py +127 -0
  66. shaapi/template/backend/core/conf.py +208 -0
  67. shaapi/template/backend/core/path_conf.py +24 -0
  68. shaapi/template/backend/core/registrar.py +195 -0
  69. shaapi/template/backend/crud/__init__.py +1 -0
  70. shaapi/template/backend/crud/crud_base.py +35 -0
  71. shaapi/template/backend/crud/crud_casbin.py +46 -0
  72. shaapi/template/backend/crud/crud_login_log.py +58 -0
  73. shaapi/template/backend/crud/crud_opera_log.py +58 -0
  74. shaapi/template/backend/crud/crud_role.py +128 -0
  75. shaapi/template/backend/crud/crud_user.py +267 -0
  76. shaapi/template/backend/database/__init__.py +0 -0
  77. shaapi/template/backend/database/db_postgres.py +125 -0
  78. shaapi/template/backend/database/db_redis.py +62 -0
  79. shaapi/template/backend/entrypoint-api.sh +19 -0
  80. shaapi/template/backend/lang/en/app.py +18 -0
  81. shaapi/template/backend/lang/en/auth.py +10 -0
  82. shaapi/template/backend/lang/fr/app.py +18 -0
  83. shaapi/template/backend/lang/fr/auth.py +10 -0
  84. shaapi/template/backend/main.py +54 -0
  85. shaapi/template/backend/middleware/__init__.py +1 -0
  86. shaapi/template/backend/middleware/access_middleware.py +19 -0
  87. shaapi/template/backend/middleware/i18n_middleware.py +19 -0
  88. shaapi/template/backend/middleware/jwt_auth_middleware.py +73 -0
  89. shaapi/template/backend/middleware/opera_log_middleware.py +179 -0
  90. shaapi/template/backend/middleware/state_middleware.py +26 -0
  91. shaapi/template/backend/models/__init__.py +10 -0
  92. shaapi/template/backend/models/associations.py +20 -0
  93. shaapi/template/backend/models/casbin_rule.py +30 -0
  94. shaapi/template/backend/models/login_log.py +28 -0
  95. shaapi/template/backend/models/opera_log.py +36 -0
  96. shaapi/template/backend/models/role.py +27 -0
  97. shaapi/template/backend/models/user.py +30 -0
  98. shaapi/template/backend/seeder/json/admin.json +15 -0
  99. shaapi/template/backend/seeder/json/user.json +15 -0
  100. shaapi/template/backend/seeder/run.py +34 -0
  101. shaapi/template/backend/static/ip2region.xdb +0 -0
  102. shaapi/template/backend/templates/build/meet.html +169 -0
  103. shaapi/template/backend/templates/build/new_account.html +373 -0
  104. shaapi/template/backend/templates/build/reset-password.html +170 -0
  105. shaapi/template/backend/templates/build/test_email.html +25 -0
  106. shaapi/template/backend/templates/build/welcome-one-1.html +160 -0
  107. shaapi/template/backend/templates/build/welcome-one.html +178 -0
  108. shaapi/template/backend/templates/build/welcome-two.html +234 -0
  109. shaapi/template/backend/templates/index.html +0 -0
  110. shaapi/template/backend/templates/src/new_account.mjml +15 -0
  111. shaapi/template/backend/templates/src/reset_password.mjml +19 -0
  112. shaapi/template/backend/templates/src/test_email.mjml +11 -0
  113. shaapi/template/backend/templates/ws/ws.html +70 -0
  114. shaapi/template/backend/utils/demo_site.py +18 -0
  115. shaapi/template/backend/utils/encrypt.py +108 -0
  116. shaapi/template/backend/utils/health_check.py +34 -0
  117. shaapi/template/backend/utils/prometheus.py +135 -0
  118. shaapi/template/backend/utils/request_parse.py +110 -0
  119. shaapi/template/backend/utils/serializers.py +75 -0
  120. shaapi/template/backend/utils/timezone.py +51 -0
  121. shaapi/template/backend/utils/trace_id.py +7 -0
  122. shaapi/template/backend/utils/translator.py +28 -0
  123. shaapi/template/devops/scripts/deploy.sh +7 -0
  124. shaapi/template/devops/scripts/setup_env.sh +62 -0
  125. shaapi/template/docker-compose.monitoring.yml +63 -0
  126. shaapi/template/docker-compose.override.yml +12 -0
  127. shaapi/template/docker-compose.yml +90 -0
  128. shaapi/template/docker-run.sh +99 -0
  129. shaapi/template/etc/dashboards/fastapi-observability.json +1044 -0
  130. shaapi/template/etc/dashboards.yaml +10 -0
  131. shaapi/template/etc/grafana/datasource.yml +79 -0
  132. shaapi/template/etc/prometheus/prometheus.yml +52 -0
  133. shaapi/template/package-lock.json +2102 -0
  134. shaapi/template/package.json +16 -0
  135. shaapi/template/pyproject.toml +78 -0
  136. shaapi/template/uv.lock +2866 -0
  137. shaapi-0.1.0.dist-info/METADATA +92 -0
  138. shaapi-0.1.0.dist-info/RECORD +141 -0
  139. shaapi-0.1.0.dist-info/WHEEL +4 -0
  140. shaapi-0.1.0.dist-info/entry_points.txt +2 -0
  141. 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
+ )