zrb 1.0.0b2__py3-none-any.whl → 1.0.0b4__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.
- zrb/__main__.py +3 -0
- zrb/builtin/llm/llm_chat.py +85 -5
- zrb/builtin/llm/previous-session.js +13 -0
- zrb/builtin/llm/tool/api.py +29 -0
- zrb/builtin/llm/tool/cli.py +1 -1
- zrb/builtin/llm/tool/rag.py +108 -145
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/client_method.py +6 -6
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/gateway_subroute.py +3 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_db_repository.py +88 -44
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/config.py +12 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_client.py +28 -22
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration/versions/3093c7336477_add_auth_tables.py +6 -6
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/repository/role_db_repository.py +43 -29
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/repository/role_repository.py +8 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/role_service.py +46 -14
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_db_repository.py +158 -20
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_repository.py +29 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_service.py +36 -14
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/subroute/auth.py +14 -14
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/permission.py +1 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/role.py +34 -6
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/session.py +2 -6
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/user.py +41 -2
- zrb/builtin/todo.py +1 -0
- zrb/config.py +23 -4
- zrb/input/any_input.py +5 -0
- zrb/input/base_input.py +6 -0
- zrb/input/bool_input.py +2 -0
- zrb/input/float_input.py +2 -0
- zrb/input/int_input.py +2 -0
- zrb/input/option_input.py +2 -0
- zrb/input/password_input.py +2 -0
- zrb/input/text_input.py +2 -0
- zrb/runner/common_util.py +1 -1
- zrb/runner/web_route/error_page/show_error_page.py +2 -1
- zrb/runner/web_route/static/resources/session/current-session.js +4 -2
- zrb/runner/web_route/static/resources/session/event.js +8 -2
- zrb/runner/web_route/task_session_api_route.py +48 -3
- zrb/task/base_task.py +14 -13
- zrb/task/llm_task.py +214 -84
- zrb/util/llm/tool.py +3 -7
- {zrb-1.0.0b2.dist-info → zrb-1.0.0b4.dist-info}/METADATA +2 -1
- {zrb-1.0.0b2.dist-info → zrb-1.0.0b4.dist-info}/RECORD +45 -43
- {zrb-1.0.0b2.dist-info → zrb-1.0.0b4.dist-info}/WHEEL +0 -0
- {zrb-1.0.0b2.dist-info → zrb-1.0.0b4.dist-info}/entry_points.txt +0 -0
@@ -4,10 +4,15 @@ from typing import Any, Callable, Generic, Type, TypeVar
|
|
4
4
|
|
5
5
|
import ulid
|
6
6
|
from my_app_name.common.error import InvalidValueError, NotFoundError
|
7
|
-
from my_app_name.common.parser_factory import
|
7
|
+
from my_app_name.common.parser_factory import (
|
8
|
+
parse_filter_param as default_parse_filter_param,
|
9
|
+
)
|
10
|
+
from my_app_name.common.parser_factory import (
|
11
|
+
parse_sort_param as default_parse_sort_param,
|
12
|
+
)
|
8
13
|
from sqlalchemy import Engine, delete, func, insert, select, update
|
9
14
|
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession
|
10
|
-
from sqlalchemy.sql import Select
|
15
|
+
from sqlalchemy.sql import ClauseElement, ColumnElement, Select
|
11
16
|
from sqlmodel import Session, SQLModel
|
12
17
|
|
13
18
|
DBModel = TypeVar("DBModel", bound=SQLModel)
|
@@ -24,16 +29,48 @@ class BaseDBRepository(Generic[DBModel, ResponseModel, CreateModel, UpdateModel]
|
|
24
29
|
entity_name: str = "entity"
|
25
30
|
column_preprocessors: dict[str, Callable[[Any], Any]] = {}
|
26
31
|
|
27
|
-
def __init__(
|
32
|
+
def __init__(
|
33
|
+
self,
|
34
|
+
engine: Engine | AsyncEngine,
|
35
|
+
filter_param_parser: (
|
36
|
+
Callable[[SQLModel, str], list[ClauseElement]] | None
|
37
|
+
) = None,
|
38
|
+
sort_param_parser: Callable[[SQLModel, str], list[ColumnElement]] | None = None,
|
39
|
+
):
|
28
40
|
self.engine = engine
|
29
|
-
self.
|
41
|
+
self._is_async = isinstance(engine, AsyncEngine)
|
42
|
+
self._parse_filter_param = (
|
43
|
+
filter_param_parser if filter_param_parser else default_parse_filter_param
|
44
|
+
)
|
45
|
+
self._parse_sort_param = (
|
46
|
+
sort_param_parser if sort_param_parser else default_parse_sort_param
|
47
|
+
)
|
48
|
+
|
49
|
+
@property
|
50
|
+
def is_async(self) -> bool:
|
51
|
+
return self._is_async
|
30
52
|
|
31
53
|
def _select(self) -> Select:
|
54
|
+
"""
|
55
|
+
This method is used to contruct select statement for get, get_by_id and get_by_ids.
|
56
|
+
To parse the result of the statement, make sure you override _rows_to_response as well.
|
57
|
+
"""
|
32
58
|
return select(self.db_model)
|
33
59
|
|
34
|
-
def _rows_to_responses(self, rows: list[tuple[Any]]) -> list[ResponseModel]:
|
60
|
+
def _rows_to_responses(self, rows: list[tuple[Any, ...]]) -> list[ResponseModel]:
|
61
|
+
"""
|
62
|
+
This method is used to parse the result of select statement generated by _select.
|
63
|
+
"""
|
35
64
|
return [self.response_model.model_validate(row[0]) for row in rows]
|
36
65
|
|
66
|
+
async def _select_to_response(
|
67
|
+
self, query_modifier: Callable[[Select], Any]
|
68
|
+
) -> list[ResponseModel]:
|
69
|
+
statement = query_modifier(self._select())
|
70
|
+
async with self._session_scope() as session:
|
71
|
+
result = await self._execute_statement(session, statement)
|
72
|
+
return self._rows_to_responses(result.all())
|
73
|
+
|
37
74
|
def _ensure_one(self, responses: list[ResponseModel]) -> ResponseModel:
|
38
75
|
if not responses:
|
39
76
|
raise NotFoundError(f"{self.entity_name} not found")
|
@@ -41,6 +78,22 @@ class BaseDBRepository(Generic[DBModel, ResponseModel, CreateModel, UpdateModel]
|
|
41
78
|
raise InvalidValueError(f"Duplicate {self.entity_name}")
|
42
79
|
return responses[0]
|
43
80
|
|
81
|
+
def _model_to_data_dict(
|
82
|
+
self, data: SQLModel, **additional_data: Any
|
83
|
+
) -> dict[str, Any]:
|
84
|
+
"""
|
85
|
+
This method transform SQLModel into dictionary for insert/update operation.
|
86
|
+
"""
|
87
|
+
data_dict = data.model_dump(exclude_unset=True)
|
88
|
+
data_dict.update(additional_data)
|
89
|
+
for key, preprocessor in self.column_preprocessors.items():
|
90
|
+
if key not in data_dict:
|
91
|
+
continue
|
92
|
+
if not hasattr(self.db_model, key):
|
93
|
+
raise InvalidValueError(f"Invalid {self.entity_name} property: {key}")
|
94
|
+
data_dict[key] = preprocessor(data_dict[key])
|
95
|
+
return data_dict
|
96
|
+
|
44
97
|
@asynccontextmanager
|
45
98
|
async def _session_scope(self):
|
46
99
|
if self.is_async:
|
@@ -65,25 +118,18 @@ class BaseDBRepository(Generic[DBModel, ResponseModel, CreateModel, UpdateModel]
|
|
65
118
|
return session.execute(statement)
|
66
119
|
|
67
120
|
async def get_by_id(self, id: str) -> ResponseModel:
|
68
|
-
|
69
|
-
|
70
|
-
result = await self._execute_statement(session, statement)
|
71
|
-
responses = self._rows_to_responses(result.all())
|
72
|
-
return self._ensure_one(responses)
|
121
|
+
rows = await self._select_to_response(lambda q: q.where(self.db_model.id == id))
|
122
|
+
return self._ensure_one(rows)
|
73
123
|
|
74
124
|
async def get_by_ids(self, id_list: list[str]) -> list[ResponseModel]:
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
return [
|
79
|
-
self.db_model(**entity.model_dump())
|
80
|
-
for entity in result.scalars().all()
|
81
|
-
]
|
125
|
+
return await self._select_to_response(
|
126
|
+
lambda q: q.where(self.db_model.id.in_(id_list))
|
127
|
+
)
|
82
128
|
|
83
129
|
async def count(self, filter: str | None = None) -> int:
|
84
130
|
count_statement = select(func.count(1)).select_from(self.db_model)
|
85
131
|
if filter:
|
86
|
-
filter_param =
|
132
|
+
filter_param = self._parse_filter_param(self.db_model, filter)
|
87
133
|
count_statement = count_statement.where(*filter_param)
|
88
134
|
async with self._session_scope() as session:
|
89
135
|
result = await self._execute_statement(session, count_statement)
|
@@ -96,33 +142,31 @@ class BaseDBRepository(Generic[DBModel, ResponseModel, CreateModel, UpdateModel]
|
|
96
142
|
filter: str | None = None,
|
97
143
|
sort: str | None = None,
|
98
144
|
) -> list[ResponseModel]:
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
if sort:
|
105
|
-
sort_param = parse_sort_param(self.db_model, sort)
|
106
|
-
statement = statement.order_by(*sort_param)
|
107
|
-
async with self._session_scope() as session:
|
108
|
-
result = await self._execute_statement(session, statement)
|
109
|
-
return [
|
110
|
-
self.db_model(**entity.model_dump())
|
111
|
-
for entity in result.scalars().all()
|
112
|
-
]
|
145
|
+
return await self._select_to_response(
|
146
|
+
self._get_pagination_query_modifier(
|
147
|
+
page=page, page_size=page_size, filter=filter, sort=sort
|
148
|
+
)
|
149
|
+
)
|
113
150
|
|
114
|
-
def
|
115
|
-
self,
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
151
|
+
def _get_pagination_query_modifier(
|
152
|
+
self,
|
153
|
+
page: int = 1,
|
154
|
+
page_size: int = 10,
|
155
|
+
filter: str | None = None,
|
156
|
+
sort: str | None = None,
|
157
|
+
) -> Callable[[Select], Any]:
|
158
|
+
def pagination_query_modifier(statement: Select) -> Any:
|
159
|
+
offset = (page - 1) * page_size
|
160
|
+
statement = statement.offset(offset).limit(page_size)
|
161
|
+
if filter:
|
162
|
+
filter_param = self._parse_filter_param(self.db_model, filter)
|
163
|
+
statement = statement.where(*filter_param)
|
164
|
+
if sort:
|
165
|
+
sort_param = self._parse_sort_param(self.db_model, sort)
|
166
|
+
statement = statement.order_by(*sort_param)
|
167
|
+
return statement
|
168
|
+
|
169
|
+
return pagination_query_modifier
|
126
170
|
|
127
171
|
async def create(self, data: CreateModel) -> DBModel:
|
128
172
|
now = datetime.datetime.now(datetime.timezone.utc)
|
@@ -50,5 +50,17 @@ APP_AUTH_SUPER_USER = os.getenv("MY_APP_NAME_AUTH_SUPER_USER", "admin")
|
|
50
50
|
APP_AUTH_SUPER_USER_PASSWORD = os.getenv(
|
51
51
|
"MY_APP_NAME_AUTH_SUPER_USER_PASSWORD", "my-secure-password"
|
52
52
|
)
|
53
|
+
APP_AUTH_GUEST_USER = os.getenv("MY_APP_NAME_AUTH_GUEST_USER", "user")
|
54
|
+
APP_AUTH_GUEST_USER_PERMISSIONS = (
|
55
|
+
permission_name.strip()
|
56
|
+
for permission_name in os.getenv(
|
57
|
+
"MY_APP_NAME_AUTH_GUEST_USER_PERMISSIONS", ""
|
58
|
+
).split(",")
|
59
|
+
if permission_name.strip() != ""
|
60
|
+
)
|
61
|
+
APP_MAX_PARALLEL_SESSION = int(os.getenv("MY_APP_NAME_MAX_PARALLEL_SESSION", "1"))
|
62
|
+
APP_SESSION_EXPIRE_MINUTES = int(
|
63
|
+
os.getenv("MY_APP_NAME_SESSION_EXPIRE_MINUTES", "1440")
|
64
|
+
)
|
53
65
|
|
54
66
|
APP_AUTH_BASE_URL = os.getenv("MY_APP_NAME_AUTH_BASE_URL", "http://localhost:3001")
|
zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_client.py
CHANGED
@@ -8,15 +8,15 @@ from my_app_name.schema.permission import (
|
|
8
8
|
)
|
9
9
|
from my_app_name.schema.role import (
|
10
10
|
MultipleRoleResponse,
|
11
|
-
|
11
|
+
RoleCreateWithPermissionsAndAudit,
|
12
12
|
RoleResponse,
|
13
|
-
|
13
|
+
RoleUpdateWithPermissionsAndAudit,
|
14
14
|
)
|
15
15
|
from my_app_name.schema.user import (
|
16
16
|
MultipleUserResponse,
|
17
|
-
|
17
|
+
UserCreateWithRolesAndAudit,
|
18
18
|
UserResponse,
|
19
|
-
|
19
|
+
UserUpdateWithRolesAndAudit,
|
20
20
|
)
|
21
21
|
|
22
22
|
|
@@ -38,18 +38,18 @@ class AuthClient(ABC):
|
|
38
38
|
) -> MultiplePermissionResponse:
|
39
39
|
"""Get permissions by filter and sort"""
|
40
40
|
|
41
|
+
@abstractmethod
|
42
|
+
async def create_permission_bulk(
|
43
|
+
self, data: list[PermissionCreateWithAudit]
|
44
|
+
) -> list[PermissionResponse]:
|
45
|
+
"""Create new permissions"""
|
46
|
+
|
41
47
|
@abstractmethod
|
42
48
|
async def create_permission(
|
43
49
|
self, data: PermissionCreateWithAudit
|
44
50
|
) -> PermissionResponse:
|
45
51
|
"""Create a new permission"""
|
46
52
|
|
47
|
-
@abstractmethod
|
48
|
-
async def create_permission(
|
49
|
-
self, data: list[PermissionCreateWithAudit]
|
50
|
-
) -> list[PermissionResponse]:
|
51
|
-
"""Create new permissions"""
|
52
|
-
|
53
53
|
@abstractmethod
|
54
54
|
async def update_permission_bulk(
|
55
55
|
self, permission_ids: list[str], data: PermissionUpdateWithAudit
|
@@ -91,22 +91,26 @@ class AuthClient(ABC):
|
|
91
91
|
"""Get roles by filter and sort"""
|
92
92
|
|
93
93
|
@abstractmethod
|
94
|
-
async def
|
95
|
-
|
94
|
+
async def create_role_bulk(
|
95
|
+
self, data: list[RoleCreateWithPermissionsAndAudit]
|
96
|
+
) -> list[RoleResponse]:
|
97
|
+
"""Create new roles"""
|
96
98
|
|
97
99
|
@abstractmethod
|
98
|
-
async def create_role(
|
99
|
-
|
100
|
+
async def create_role(
|
101
|
+
self, data: RoleCreateWithPermissionsAndAudit
|
102
|
+
) -> RoleResponse:
|
103
|
+
"""Create a new role"""
|
100
104
|
|
101
105
|
@abstractmethod
|
102
106
|
async def update_role_bulk(
|
103
|
-
self, role_ids: list[str], data:
|
107
|
+
self, role_ids: list[str], data: RoleUpdateWithPermissionsAndAudit
|
104
108
|
) -> RoleResponse:
|
105
109
|
"""Update some roles"""
|
106
110
|
|
107
111
|
@abstractmethod
|
108
112
|
async def update_role(
|
109
|
-
self, role_id: str, data:
|
113
|
+
self, role_id: str, data: RoleUpdateWithPermissionsAndAudit
|
110
114
|
) -> RoleResponse:
|
111
115
|
"""Update a role"""
|
112
116
|
|
@@ -135,22 +139,24 @@ class AuthClient(ABC):
|
|
135
139
|
"""Get users by filter and sort"""
|
136
140
|
|
137
141
|
@abstractmethod
|
138
|
-
async def
|
139
|
-
|
142
|
+
async def create_user_bulk(
|
143
|
+
self, data: list[UserCreateWithRolesAndAudit]
|
144
|
+
) -> list[UserResponse]:
|
145
|
+
"""Create new users"""
|
140
146
|
|
141
147
|
@abstractmethod
|
142
|
-
async def create_user(self, data:
|
143
|
-
"""Create new
|
148
|
+
async def create_user(self, data: UserCreateWithRolesAndAudit) -> UserResponse:
|
149
|
+
"""Create a new user"""
|
144
150
|
|
145
151
|
@abstractmethod
|
146
152
|
async def update_user_bulk(
|
147
|
-
self, user_ids: list[str], data:
|
153
|
+
self, user_ids: list[str], data: UserUpdateWithRolesAndAudit
|
148
154
|
) -> UserResponse:
|
149
155
|
"""Update some users"""
|
150
156
|
|
151
157
|
@abstractmethod
|
152
158
|
async def update_user(
|
153
|
-
self, user_id: str, data:
|
159
|
+
self, user_id: str, data: UserUpdateWithRolesAndAudit
|
154
160
|
) -> UserResponse:
|
155
161
|
"""Update a user"""
|
156
162
|
|
@@ -32,7 +32,7 @@ def upgrade() -> None:
|
|
32
32
|
sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
33
33
|
sa.PrimaryKeyConstraint("id"),
|
34
34
|
)
|
35
|
-
op.create_index(op.f("ix_permission_name"), "permission", ["name"], unique=
|
35
|
+
op.create_index(op.f("ix_permission_name"), "permission", ["name"], unique=True)
|
36
36
|
op.create_index(
|
37
37
|
op.f("ix_permission_created_at"), "permission", ["created_at"], unique=False
|
38
38
|
)
|
@@ -57,7 +57,7 @@ def upgrade() -> None:
|
|
57
57
|
sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
58
58
|
sa.PrimaryKeyConstraint("id"),
|
59
59
|
)
|
60
|
-
op.create_index(op.f("ix_role_name"), "role", ["name"], unique=
|
60
|
+
op.create_index(op.f("ix_role_name"), "role", ["name"], unique=True)
|
61
61
|
op.create_index(op.f("ix_role_created_at"), "role", ["created_at"], unique=False)
|
62
62
|
op.create_index(op.f("ix_role_created_by"), "role", ["created_by"], unique=False)
|
63
63
|
op.create_index(op.f("ix_role_updated_at"), "role", ["updated_at"], unique=False)
|
@@ -93,7 +93,7 @@ def upgrade() -> None:
|
|
93
93
|
sa.Column("updated_by", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
94
94
|
sa.PrimaryKeyConstraint("id"),
|
95
95
|
)
|
96
|
-
op.create_index(op.f("ix_user_username"), "user", ["username"], unique=
|
96
|
+
op.create_index(op.f("ix_user_username"), "user", ["username"], unique=True)
|
97
97
|
op.create_index(op.f("ix_user_created_at"), "user", ["created_at"], unique=False)
|
98
98
|
op.create_index(op.f("ix_user_created_by"), "user", ["created_by"], unique=False)
|
99
99
|
op.create_index(op.f("ix_user_updated_at"), "user", ["updated_at"], unique=False)
|
@@ -115,17 +115,17 @@ def upgrade() -> None:
|
|
115
115
|
"session",
|
116
116
|
sa.Column("id", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
117
117
|
sa.Column("user_id", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
118
|
-
sa.Column("
|
119
|
-
sa.Column("os", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
120
|
-
sa.Column("browser", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
118
|
+
sa.Column("token", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
121
119
|
sa.PrimaryKeyConstraint("id"),
|
122
120
|
)
|
123
121
|
op.create_index(op.f("ix_session_user_id"), "session", ["user_id"], unique=False)
|
122
|
+
op.create_index(op.f("ix_session_token"), "session", ["token"], unique=True)
|
124
123
|
# ### end Alembic commands ###
|
125
124
|
|
126
125
|
|
127
126
|
def downgrade() -> None:
|
128
127
|
# ### commands auto generated by Alembic - please adjust! ###
|
128
|
+
op.drop_index(op.f("ix_session_token"), table_name="session")
|
129
129
|
op.drop_index(op.f("ix_session_user_id"), table_name="session")
|
130
130
|
op.drop_table("session")
|
131
131
|
|
@@ -1,5 +1,7 @@
|
|
1
|
+
import datetime
|
1
2
|
from typing import Any
|
2
3
|
|
4
|
+
import ulid
|
3
5
|
from my_app_name.common.base_db_repository import BaseDBRepository
|
4
6
|
from my_app_name.module.auth.service.role.repository.role_repository import (
|
5
7
|
RoleRepository,
|
@@ -13,7 +15,7 @@ from my_app_name.schema.role import (
|
|
13
15
|
RoleUpdateWithAudit,
|
14
16
|
)
|
15
17
|
from sqlalchemy.sql import Select
|
16
|
-
from sqlmodel import select
|
18
|
+
from sqlmodel import delete, insert, select
|
17
19
|
|
18
20
|
|
19
21
|
class RoleDBRepository(
|
@@ -35,41 +37,53 @@ class RoleDBRepository(
|
|
35
37
|
return (
|
36
38
|
select(Role, Permission)
|
37
39
|
.join(RolePermission, RolePermission.role_id == Role.id, isouter=True)
|
38
|
-
.join(
|
40
|
+
.join(
|
41
|
+
Permission, Permission.id == RolePermission.permission_id, isouter=True
|
42
|
+
)
|
39
43
|
)
|
40
44
|
|
41
|
-
def _rows_to_responses(self, rows: list[tuple[
|
45
|
+
def _rows_to_responses(self, rows: list[tuple[Any, ...]]) -> list[RoleResponse]:
|
42
46
|
role_map: dict[str, dict[str, Any]] = {}
|
47
|
+
role_permission_map: dict[str, list[str]] = {}
|
43
48
|
for role, permission in rows:
|
44
49
|
if role.id not in role_map:
|
45
|
-
role_map[role.id] = {"role": role, "permissions":
|
46
|
-
|
47
|
-
|
50
|
+
role_map[role.id] = {"role": role, "permissions": []}
|
51
|
+
role_permission_map[role.id] = []
|
52
|
+
if (
|
53
|
+
permission is not None
|
54
|
+
and permission.id not in role_permission_map[role.id]
|
55
|
+
):
|
56
|
+
role_permission_map[role.id].append(permission.id)
|
57
|
+
role_map[role.id]["permissions"].append(permission.model_dump())
|
48
58
|
return [
|
49
|
-
RoleResponse(
|
50
|
-
**data["role"].model_dump(), permissions=list(data["permissions"])
|
51
|
-
)
|
59
|
+
RoleResponse(**data["role"].model_dump(), permissions=data["permissions"])
|
52
60
|
for data in role_map.values()
|
53
61
|
]
|
54
62
|
|
55
|
-
async def add_permissions(self,
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
63
|
+
async def add_permissions(self, data: dict[str, list[str]], created_by: str):
|
64
|
+
now = datetime.datetime.now(datetime.timezone.utc)
|
65
|
+
data_dict_list: list[dict[str, Any]] = []
|
66
|
+
for role_id, permission_ids in data.items():
|
67
|
+
for permission_id in permission_ids:
|
68
|
+
data_dict_list.append(
|
69
|
+
self._model_to_data_dict(
|
70
|
+
RolePermission(
|
71
|
+
id=ulid.new().str,
|
72
|
+
role_id=role_id,
|
73
|
+
permission_id=permission_id,
|
74
|
+
created_at=now,
|
75
|
+
created_by=created_by,
|
76
|
+
)
|
77
|
+
)
|
78
|
+
)
|
79
|
+
async with self._session_scope() as session:
|
80
|
+
await self._execute_statement(
|
81
|
+
session, insert(RolePermission).values(data_dict_list)
|
82
|
+
)
|
69
83
|
|
70
|
-
def
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
84
|
+
async def remove_all_permissions(self, role_ids: list[str] = []):
|
85
|
+
async with self._session_scope() as session:
|
86
|
+
await self._execute_statement(
|
87
|
+
session,
|
88
|
+
delete(RolePermission).where(RolePermission.role_id._in(role_ids)),
|
89
|
+
)
|
@@ -18,6 +18,14 @@ class RoleRepository(ABC):
|
|
18
18
|
async def get_by_ids(self, id_list: list[str]) -> RoleResponse:
|
19
19
|
"""Get roles by ids"""
|
20
20
|
|
21
|
+
@abstractmethod
|
22
|
+
async def add_permissions(self, data: dict[str, list[str]], created_by: str):
|
23
|
+
"""Adding permissions to roles"""
|
24
|
+
|
25
|
+
@abstractmethod
|
26
|
+
async def remove_all_permissions(self, role_ids: list[str] = []):
|
27
|
+
"""Remove permissions from roles"""
|
28
|
+
|
21
29
|
@abstractmethod
|
22
30
|
async def get(
|
23
31
|
self,
|
@@ -6,9 +6,9 @@ from my_app_name.module.auth.service.role.repository.role_repository import (
|
|
6
6
|
)
|
7
7
|
from my_app_name.schema.role import (
|
8
8
|
MultipleRoleResponse,
|
9
|
-
|
9
|
+
RoleCreateWithPermissionsAndAudit,
|
10
10
|
RoleResponse,
|
11
|
-
|
11
|
+
RoleUpdateWithPermissionsAndAudit,
|
12
12
|
)
|
13
13
|
|
14
14
|
|
@@ -42,35 +42,61 @@ class RoleService(BaseService):
|
|
42
42
|
count = await self.role_repository.count(filter)
|
43
43
|
return MultipleRoleResponse(data=roles, count=count)
|
44
44
|
|
45
|
-
@BaseService.route(
|
46
|
-
"/api/v1/roles",
|
47
|
-
methods=["post"],
|
48
|
-
response_model=RoleResponse,
|
49
|
-
)
|
50
|
-
async def create_role(self, data: RoleCreateWithAudit) -> RoleResponse:
|
51
|
-
role = await self.role_repository.create(data)
|
52
|
-
return await self.role_repository.get_by_id(role.id)
|
53
|
-
|
54
45
|
@BaseService.route(
|
55
46
|
"/api/v1/roles/bulk",
|
56
47
|
methods=["post"],
|
57
48
|
response_model=list[RoleResponse],
|
58
49
|
)
|
59
50
|
async def create_role_bulk(
|
60
|
-
self, data: list[
|
51
|
+
self, data: list[RoleCreateWithPermissionsAndAudit]
|
61
52
|
) -> list[RoleResponse]:
|
53
|
+
permission_ids = [row.get_permission_ids() for row in data]
|
54
|
+
data = [row.get_role_create_with_audit() for row in data]
|
62
55
|
roles = await self.role_repository.create_bulk(data)
|
56
|
+
if len(roles) > 0:
|
57
|
+
created_by = roles[0].created_by
|
58
|
+
await self.role_repository.add_permissions(
|
59
|
+
data={role.id: permission_ids[i] for i, role in enumerate(roles)},
|
60
|
+
created_by=created_by,
|
61
|
+
)
|
63
62
|
return await self.role_repository.get_by_ids([role.id for role in roles])
|
64
63
|
|
64
|
+
@BaseService.route(
|
65
|
+
"/api/v1/roles",
|
66
|
+
methods=["post"],
|
67
|
+
response_model=RoleResponse,
|
68
|
+
)
|
69
|
+
async def create_role(
|
70
|
+
self, data: RoleCreateWithPermissionsAndAudit
|
71
|
+
) -> RoleResponse:
|
72
|
+
permission_ids = data.get_permission_ids()
|
73
|
+
data = data.get_role_create_with_audit()
|
74
|
+
role = await self.role_repository.create(data)
|
75
|
+
await self.role_repository.add_permissions(
|
76
|
+
data={role.id: permission_ids}, created_by=role.created_by
|
77
|
+
)
|
78
|
+
return await self.role_repository.get_by_id(role.id)
|
79
|
+
|
65
80
|
@BaseService.route(
|
66
81
|
"/api/v1/roles/bulk",
|
67
82
|
methods=["put"],
|
68
83
|
response_model=RoleResponse,
|
69
84
|
)
|
70
85
|
async def update_role_bulk(
|
71
|
-
self, role_ids: list[str], data:
|
86
|
+
self, role_ids: list[str], data: RoleUpdateWithPermissionsAndAudit
|
72
87
|
) -> RoleResponse:
|
88
|
+
permission_ids = [row.get_permission_ids() for row in data]
|
89
|
+
data = [row.get_role_update_with_audit() for row in data]
|
73
90
|
roles = await self.role_repository.update_bulk(role_ids, data)
|
91
|
+
if len(roles) > 0:
|
92
|
+
updated_by = roles[0].updated_by
|
93
|
+
await self.role_repository.remove_all_permissions(
|
94
|
+
[role.id for role in roles]
|
95
|
+
)
|
96
|
+
await self.role_repository.add_permissions(
|
97
|
+
data={role.id: permission_ids[i] for i, role in enumerate(roles)},
|
98
|
+
created_by=updated_by,
|
99
|
+
)
|
74
100
|
return await self.role_repository.get_by_ids([role.id for role in roles])
|
75
101
|
|
76
102
|
@BaseService.route(
|
@@ -79,9 +105,15 @@ class RoleService(BaseService):
|
|
79
105
|
response_model=RoleResponse,
|
80
106
|
)
|
81
107
|
async def update_role(
|
82
|
-
self, role_id: str, data:
|
108
|
+
self, role_id: str, data: RoleUpdateWithPermissionsAndAudit
|
83
109
|
) -> RoleResponse:
|
110
|
+
permission_ids = data.get_permission_ids()
|
111
|
+
data = data.get_role_update_with_audit()
|
84
112
|
role = await self.role_repository.update(role_id, data)
|
113
|
+
await self.role_repository.remove_all_permissions([role.id])
|
114
|
+
await self.role_repository.add_permissions(
|
115
|
+
data={role.id: permission_ids}, created_by=role.updated_by
|
116
|
+
)
|
85
117
|
return await self.role_repository.get_by_id(role.id)
|
86
118
|
|
87
119
|
@BaseService.route(
|