data-syncmaster 0.1.1__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.
- data_syncmaster-0.1.1.dist-info/LICENSE.txt +203 -0
- data_syncmaster-0.1.1.dist-info/METADATA +115 -0
- data_syncmaster-0.1.1.dist-info/RECORD +110 -0
- data_syncmaster-0.1.1.dist-info/WHEEL +4 -0
- syncmaster/__init__.py +6 -0
- syncmaster/backend/__init__.py +2 -0
- syncmaster/backend/api/__init__.py +2 -0
- syncmaster/backend/api/deps.py +20 -0
- syncmaster/backend/api/monitoring.py +10 -0
- syncmaster/backend/api/router.py +10 -0
- syncmaster/backend/api/v1/__init__.py +2 -0
- syncmaster/backend/api/v1/auth/__init__.py +2 -0
- syncmaster/backend/api/v1/auth/router.py +32 -0
- syncmaster/backend/api/v1/auth/utils.py +26 -0
- syncmaster/backend/api/v1/connections.py +300 -0
- syncmaster/backend/api/v1/groups.py +225 -0
- syncmaster/backend/api/v1/queue.py +148 -0
- syncmaster/backend/api/v1/router.py +18 -0
- syncmaster/backend/api/v1/transfers/__init__.py +2 -0
- syncmaster/backend/api/v1/transfers/router.py +469 -0
- syncmaster/backend/api/v1/transfers/utils.py +17 -0
- syncmaster/backend/api/v1/users.py +75 -0
- syncmaster/backend/export_openapi_schema.py +26 -0
- syncmaster/backend/handler.py +203 -0
- syncmaster/backend/logger.py +2 -0
- syncmaster/backend/main.py +63 -0
- syncmaster/backend/pre_start.py +94 -0
- syncmaster/backend/services/__init__.py +4 -0
- syncmaster/backend/services/auth.py +58 -0
- syncmaster/backend/services/unit_of_work.py +44 -0
- syncmaster/config.py +110 -0
- syncmaster/db/__init__.py +2 -0
- syncmaster/db/alembic.ini +41 -0
- syncmaster/db/base.py +28 -0
- syncmaster/db/factory.py +37 -0
- syncmaster/db/migrations/README +1 -0
- syncmaster/db/migrations/__init__.py +2 -0
- syncmaster/db/migrations/env.py +87 -0
- syncmaster/db/migrations/script.py.mako +24 -0
- syncmaster/db/migrations/versions/2023-11-23_478240cdad4b_init.py +242 -0
- syncmaster/db/migrations/versions/__init__.py +2 -0
- syncmaster/db/mixins.py +33 -0
- syncmaster/db/models.py +194 -0
- syncmaster/db/repositories/__init__.py +22 -0
- syncmaster/db/repositories/base.py +109 -0
- syncmaster/db/repositories/connection.py +138 -0
- syncmaster/db/repositories/credentials_repository.py +87 -0
- syncmaster/db/repositories/group.py +264 -0
- syncmaster/db/repositories/queue.py +195 -0
- syncmaster/db/repositories/repository_with_owner.py +115 -0
- syncmaster/db/repositories/run.py +78 -0
- syncmaster/db/repositories/transfer.py +202 -0
- syncmaster/db/repositories/user.py +72 -0
- syncmaster/db/repositories/utils.py +25 -0
- syncmaster/db/utils.py +31 -0
- syncmaster/dto/__init__.py +2 -0
- syncmaster/dto/connections.py +60 -0
- syncmaster/dto/transfers.py +46 -0
- syncmaster/exceptions/__init__.py +13 -0
- syncmaster/exceptions/base.py +12 -0
- syncmaster/exceptions/connection.py +28 -0
- syncmaster/exceptions/credentials.py +8 -0
- syncmaster/exceptions/group.py +27 -0
- syncmaster/exceptions/queue.py +16 -0
- syncmaster/exceptions/run.py +19 -0
- syncmaster/exceptions/transfer.py +39 -0
- syncmaster/exceptions/user.py +11 -0
- syncmaster/schemas/__init__.py +2 -0
- syncmaster/schemas/v1/__init__.py +54 -0
- syncmaster/schemas/v1/auth.py +12 -0
- syncmaster/schemas/v1/connection_types.py +9 -0
- syncmaster/schemas/v1/connections/__init__.py +2 -0
- syncmaster/schemas/v1/connections/connection.py +146 -0
- syncmaster/schemas/v1/connections/hdfs.py +40 -0
- syncmaster/schemas/v1/connections/hive.py +40 -0
- syncmaster/schemas/v1/connections/oracle.py +58 -0
- syncmaster/schemas/v1/connections/postgres.py +48 -0
- syncmaster/schemas/v1/connections/s3.py +66 -0
- syncmaster/schemas/v1/file_formats.py +7 -0
- syncmaster/schemas/v1/groups.py +39 -0
- syncmaster/schemas/v1/page.py +40 -0
- syncmaster/schemas/v1/queue.py +32 -0
- syncmaster/schemas/v1/status.py +16 -0
- syncmaster/schemas/v1/transfer_types.py +6 -0
- syncmaster/schemas/v1/transfers/__init__.py +172 -0
- syncmaster/schemas/v1/transfers/db.py +23 -0
- syncmaster/schemas/v1/transfers/file/__init__.py +2 -0
- syncmaster/schemas/v1/transfers/file/base.py +47 -0
- syncmaster/schemas/v1/transfers/file/hdfs.py +27 -0
- syncmaster/schemas/v1/transfers/file/s3.py +27 -0
- syncmaster/schemas/v1/transfers/file_format.py +29 -0
- syncmaster/schemas/v1/transfers/run.py +37 -0
- syncmaster/schemas/v1/transfers/strategy.py +15 -0
- syncmaster/schemas/v1/types.py +5 -0
- syncmaster/schemas/v1/users.py +83 -0
- syncmaster/worker/__init__.py +2 -0
- syncmaster/worker/base.py +14 -0
- syncmaster/worker/config.py +18 -0
- syncmaster/worker/controller.py +127 -0
- syncmaster/worker/handlers/__init__.py +2 -0
- syncmaster/worker/handlers/base.py +49 -0
- syncmaster/worker/handlers/file/__init__.py +2 -0
- syncmaster/worker/handlers/file/base.py +56 -0
- syncmaster/worker/handlers/file/hdfs.py +14 -0
- syncmaster/worker/handlers/file/s3.py +20 -0
- syncmaster/worker/handlers/hive.py +41 -0
- syncmaster/worker/handlers/oracle.py +48 -0
- syncmaster/worker/handlers/postgres.py +47 -0
- syncmaster/worker/spark.py +93 -0
- syncmaster/worker/transfer.py +85 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023-2024 MTS (Mobile Telesystems)
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import get_args
|
|
5
|
+
|
|
6
|
+
from fastapi import APIRouter, Depends, Query, status
|
|
7
|
+
from pydantic import SecretStr
|
|
8
|
+
|
|
9
|
+
from syncmaster.backend.api.deps import UnitOfWorkMarker
|
|
10
|
+
from syncmaster.backend.services import UnitOfWork, get_user
|
|
11
|
+
from syncmaster.db.models import User
|
|
12
|
+
from syncmaster.db.utils import Permission
|
|
13
|
+
from syncmaster.exceptions import ActionNotAllowedError
|
|
14
|
+
from syncmaster.exceptions.connection import (
|
|
15
|
+
ConnectionDeleteError,
|
|
16
|
+
ConnectionNotFoundError,
|
|
17
|
+
)
|
|
18
|
+
from syncmaster.exceptions.credentials import AuthDataNotFoundError
|
|
19
|
+
from syncmaster.exceptions.group import GroupNotFoundError
|
|
20
|
+
from syncmaster.schemas.v1.connection_types import ORACLE_TYPE, POSTGRES_TYPE
|
|
21
|
+
from syncmaster.schemas.v1.connections.connection import (
|
|
22
|
+
ConnectionCopySchema,
|
|
23
|
+
ConnectionPageSchema,
|
|
24
|
+
CreateConnectionSchema,
|
|
25
|
+
ReadConnectionSchema,
|
|
26
|
+
UpdateConnectionSchema,
|
|
27
|
+
)
|
|
28
|
+
from syncmaster.schemas.v1.page import MetaPageSchema
|
|
29
|
+
from syncmaster.schemas.v1.status import StatusResponseSchema
|
|
30
|
+
|
|
31
|
+
router = APIRouter(tags=["Connections"])
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
CONNECTION_TYPES = ORACLE_TYPE, POSTGRES_TYPE
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@router.get("/connections")
|
|
38
|
+
async def read_connections(
|
|
39
|
+
group_id: int,
|
|
40
|
+
page: int = Query(gt=0, default=1),
|
|
41
|
+
page_size: int = Query(gt=0, le=200, default=20),
|
|
42
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
43
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
44
|
+
) -> ConnectionPageSchema:
|
|
45
|
+
"""Return connections in page format"""
|
|
46
|
+
resource_role = await unit_of_work.connection.get_group_permission(
|
|
47
|
+
user=current_user,
|
|
48
|
+
group_id=group_id,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if resource_role == Permission.NONE:
|
|
52
|
+
raise GroupNotFoundError
|
|
53
|
+
|
|
54
|
+
pagination = await unit_of_work.connection.paginate(
|
|
55
|
+
page=page,
|
|
56
|
+
page_size=page_size,
|
|
57
|
+
group_id=group_id,
|
|
58
|
+
)
|
|
59
|
+
items: list[ReadConnectionSchema] = []
|
|
60
|
+
|
|
61
|
+
if pagination.items:
|
|
62
|
+
creds = await asyncio.gather(
|
|
63
|
+
*[unit_of_work.credentials.get_for_connection(connection_id=item.id) for item in pagination.items]
|
|
64
|
+
)
|
|
65
|
+
items = [
|
|
66
|
+
ReadConnectionSchema(
|
|
67
|
+
id=item.id,
|
|
68
|
+
group_id=item.group_id,
|
|
69
|
+
name=item.name,
|
|
70
|
+
description=item.description,
|
|
71
|
+
auth_data=creds[n_item],
|
|
72
|
+
data=item.data,
|
|
73
|
+
)
|
|
74
|
+
for n_item, item in enumerate(pagination.items)
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
return ConnectionPageSchema(
|
|
78
|
+
meta=MetaPageSchema(
|
|
79
|
+
page=pagination.page,
|
|
80
|
+
pages=pagination.pages,
|
|
81
|
+
total=pagination.total,
|
|
82
|
+
page_size=pagination.page_size,
|
|
83
|
+
has_next=pagination.has_next,
|
|
84
|
+
has_previous=pagination.has_previous,
|
|
85
|
+
next_page=pagination.next_page,
|
|
86
|
+
previous_page=pagination.previous_page,
|
|
87
|
+
),
|
|
88
|
+
items=items,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@router.post("/connections")
|
|
93
|
+
async def create_connection(
|
|
94
|
+
connection_data: CreateConnectionSchema,
|
|
95
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
96
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
97
|
+
) -> ReadConnectionSchema:
|
|
98
|
+
"""Create new connection"""
|
|
99
|
+
group_permission = await unit_of_work.connection.get_group_permission(
|
|
100
|
+
user=current_user,
|
|
101
|
+
group_id=connection_data.group_id,
|
|
102
|
+
)
|
|
103
|
+
if group_permission == Permission.NONE:
|
|
104
|
+
raise GroupNotFoundError
|
|
105
|
+
|
|
106
|
+
if group_permission < Permission.WRITE:
|
|
107
|
+
raise ActionNotAllowedError
|
|
108
|
+
|
|
109
|
+
data = connection_data.data.dict()
|
|
110
|
+
auth_data = connection_data.auth_data.dict()
|
|
111
|
+
|
|
112
|
+
# Trick to serialize SecretStr to JSON
|
|
113
|
+
for k, v in auth_data.items():
|
|
114
|
+
if isinstance(v, SecretStr):
|
|
115
|
+
auth_data[k] = v.get_secret_value()
|
|
116
|
+
async with unit_of_work:
|
|
117
|
+
connection = await unit_of_work.connection.create(
|
|
118
|
+
name=connection_data.name,
|
|
119
|
+
description=connection_data.description,
|
|
120
|
+
group_id=connection_data.group_id,
|
|
121
|
+
data=data,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
await unit_of_work.credentials.add_to_connection(
|
|
125
|
+
connection_id=connection.id,
|
|
126
|
+
data=auth_data,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
return ReadConnectionSchema(
|
|
130
|
+
id=connection.id,
|
|
131
|
+
group_id=connection.group_id,
|
|
132
|
+
name=connection.name,
|
|
133
|
+
description=connection.description,
|
|
134
|
+
data=connection.data,
|
|
135
|
+
auth_data=auth_data,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@router.get("/connections/known_types", dependencies=[Depends(get_user(is_active=True))])
|
|
140
|
+
async def read_connection_types() -> list[str]:
|
|
141
|
+
return [get_args(type_)[0] for type_ in CONNECTION_TYPES]
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@router.get("/connections/{connection_id}")
|
|
145
|
+
async def read_connection(
|
|
146
|
+
connection_id: int,
|
|
147
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
148
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
149
|
+
) -> ReadConnectionSchema:
|
|
150
|
+
resource_role = await unit_of_work.connection.get_resource_permission(
|
|
151
|
+
user=current_user,
|
|
152
|
+
resource_id=connection_id,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if resource_role == Permission.NONE:
|
|
156
|
+
raise ConnectionNotFoundError
|
|
157
|
+
|
|
158
|
+
connection = await unit_of_work.connection.read_by_id(connection_id=connection_id)
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
credentials = await unit_of_work.credentials.get_for_connection(
|
|
162
|
+
connection_id=connection.id,
|
|
163
|
+
)
|
|
164
|
+
except AuthDataNotFoundError:
|
|
165
|
+
credentials = None
|
|
166
|
+
|
|
167
|
+
return ReadConnectionSchema(
|
|
168
|
+
id=connection.id,
|
|
169
|
+
group_id=connection.group_id,
|
|
170
|
+
name=connection.name,
|
|
171
|
+
description=connection.description,
|
|
172
|
+
data=connection.data,
|
|
173
|
+
auth_data=credentials,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@router.patch("/connections/{connection_id}")
|
|
178
|
+
async def update_connection(
|
|
179
|
+
connection_id: int,
|
|
180
|
+
connection_data: UpdateConnectionSchema,
|
|
181
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
182
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
183
|
+
) -> ReadConnectionSchema:
|
|
184
|
+
resource_role = await unit_of_work.connection.get_resource_permission(
|
|
185
|
+
user=current_user,
|
|
186
|
+
resource_id=connection_id,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if resource_role == Permission.NONE:
|
|
190
|
+
raise ConnectionNotFoundError
|
|
191
|
+
|
|
192
|
+
if resource_role < Permission.WRITE:
|
|
193
|
+
raise ActionNotAllowedError
|
|
194
|
+
|
|
195
|
+
async with unit_of_work:
|
|
196
|
+
connection = await unit_of_work.connection.update(
|
|
197
|
+
connection_id=connection_id,
|
|
198
|
+
name=connection_data.name,
|
|
199
|
+
description=connection_data.description,
|
|
200
|
+
connection_data=connection_data.data.dict(exclude={"auth_data"}) if connection_data.data else {},
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
if connection_data.auth_data:
|
|
204
|
+
await unit_of_work.credentials.update(
|
|
205
|
+
connection_id=connection_id,
|
|
206
|
+
credential_data=connection_data.auth_data.dict(),
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
auth_data = await unit_of_work.credentials.get_for_connection(connection_id)
|
|
210
|
+
return ReadConnectionSchema(
|
|
211
|
+
id=connection.id,
|
|
212
|
+
group_id=connection.group_id,
|
|
213
|
+
name=connection.name,
|
|
214
|
+
description=connection.description,
|
|
215
|
+
data=connection.data,
|
|
216
|
+
auth_data=auth_data,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@router.delete("/connections/{connection_id}")
|
|
221
|
+
async def delete_connection(
|
|
222
|
+
connection_id: int,
|
|
223
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
224
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
225
|
+
) -> StatusResponseSchema:
|
|
226
|
+
resource_role = await unit_of_work.connection.get_resource_permission(
|
|
227
|
+
user=current_user,
|
|
228
|
+
resource_id=connection_id,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
if resource_role == Permission.NONE:
|
|
232
|
+
raise ConnectionNotFoundError
|
|
233
|
+
|
|
234
|
+
if resource_role < Permission.DELETE:
|
|
235
|
+
raise ActionNotAllowedError
|
|
236
|
+
|
|
237
|
+
connection = await unit_of_work.connection.read_by_id(connection_id=connection_id)
|
|
238
|
+
|
|
239
|
+
transfers = await unit_of_work.transfer.list_by_connection_id(conn_id=connection.id)
|
|
240
|
+
async with unit_of_work:
|
|
241
|
+
if not transfers:
|
|
242
|
+
await unit_of_work.connection.delete(connection_id=connection_id)
|
|
243
|
+
|
|
244
|
+
return StatusResponseSchema(
|
|
245
|
+
ok=True,
|
|
246
|
+
status_code=status.HTTP_200_OK,
|
|
247
|
+
message="Connection was deleted",
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
raise ConnectionDeleteError(
|
|
251
|
+
f"The connection has an associated transfers. Number of the connected transfers: {len(transfers)}",
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
@router.post("/connections/{connection_id}/copy_connection")
|
|
256
|
+
async def copy_connection(
|
|
257
|
+
connection_id: int,
|
|
258
|
+
copy_connection_data: ConnectionCopySchema,
|
|
259
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
260
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
261
|
+
) -> StatusResponseSchema:
|
|
262
|
+
target_source_rules = await asyncio.gather(
|
|
263
|
+
unit_of_work.connection.get_resource_permission(
|
|
264
|
+
user=current_user,
|
|
265
|
+
resource_id=connection_id,
|
|
266
|
+
),
|
|
267
|
+
unit_of_work.connection.get_group_permission(
|
|
268
|
+
user=current_user,
|
|
269
|
+
group_id=copy_connection_data.new_group_id,
|
|
270
|
+
),
|
|
271
|
+
)
|
|
272
|
+
resource_role, target_group_role = target_source_rules
|
|
273
|
+
|
|
274
|
+
if copy_connection_data.remove_source and resource_role < Permission.DELETE:
|
|
275
|
+
raise ActionNotAllowedError
|
|
276
|
+
|
|
277
|
+
if resource_role == Permission.NONE:
|
|
278
|
+
raise ConnectionNotFoundError
|
|
279
|
+
|
|
280
|
+
if target_group_role == Permission.NONE:
|
|
281
|
+
raise GroupNotFoundError
|
|
282
|
+
|
|
283
|
+
if target_group_role < Permission.WRITE:
|
|
284
|
+
raise ActionNotAllowedError
|
|
285
|
+
|
|
286
|
+
async with unit_of_work:
|
|
287
|
+
await unit_of_work.connection.copy(
|
|
288
|
+
connection_id=connection_id,
|
|
289
|
+
new_group_id=copy_connection_data.new_group_id,
|
|
290
|
+
new_name=copy_connection_data.new_name,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
if copy_connection_data.remove_source:
|
|
294
|
+
await unit_of_work.connection.delete(connection_id=connection_id)
|
|
295
|
+
|
|
296
|
+
return StatusResponseSchema(
|
|
297
|
+
ok=True,
|
|
298
|
+
status_code=status.HTTP_200_OK,
|
|
299
|
+
message="Connection was copied",
|
|
300
|
+
)
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023-2024 MTS (Mobile Telesystems)
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
from fastapi import APIRouter, Depends, Query
|
|
4
|
+
|
|
5
|
+
from syncmaster.backend.api.deps import UnitOfWorkMarker
|
|
6
|
+
from syncmaster.backend.services import UnitOfWork, get_user
|
|
7
|
+
from syncmaster.db.models import User
|
|
8
|
+
from syncmaster.db.utils import Permission
|
|
9
|
+
from syncmaster.exceptions import ActionNotAllowedError
|
|
10
|
+
from syncmaster.exceptions.group import GroupNotFoundError
|
|
11
|
+
from syncmaster.schemas.v1.groups import (
|
|
12
|
+
AddUserSchema,
|
|
13
|
+
CreateGroupSchema,
|
|
14
|
+
GroupPageSchema,
|
|
15
|
+
ReadGroupSchema,
|
|
16
|
+
UpdateGroupSchema,
|
|
17
|
+
)
|
|
18
|
+
from syncmaster.schemas.v1.status import StatusResponseSchema
|
|
19
|
+
from syncmaster.schemas.v1.users import UserPageSchemaAsGroupMember
|
|
20
|
+
|
|
21
|
+
router = APIRouter(tags=["Groups"])
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@router.get("/groups")
|
|
25
|
+
async def read_groups(
|
|
26
|
+
page: int = Query(gt=0, default=1),
|
|
27
|
+
page_size: int = Query(gt=0, le=200, default=20),
|
|
28
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
29
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
30
|
+
) -> GroupPageSchema:
|
|
31
|
+
if current_user.is_superuser:
|
|
32
|
+
pagination = await unit_of_work.group.paginate_all(
|
|
33
|
+
page=page,
|
|
34
|
+
page_size=page_size,
|
|
35
|
+
)
|
|
36
|
+
else:
|
|
37
|
+
pagination = await unit_of_work.group.paginate_for_user(
|
|
38
|
+
page=page,
|
|
39
|
+
page_size=page_size,
|
|
40
|
+
current_user_id=current_user.id,
|
|
41
|
+
)
|
|
42
|
+
return GroupPageSchema.from_pagination(pagination=pagination)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@router.post("/groups")
|
|
46
|
+
async def create_group(
|
|
47
|
+
group_data: CreateGroupSchema,
|
|
48
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
49
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
50
|
+
) -> ReadGroupSchema:
|
|
51
|
+
async with unit_of_work:
|
|
52
|
+
group = await unit_of_work.group.create(
|
|
53
|
+
name=group_data.name,
|
|
54
|
+
description=group_data.description,
|
|
55
|
+
owner_id=current_user.id,
|
|
56
|
+
)
|
|
57
|
+
return ReadGroupSchema.from_orm(group)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@router.get("/groups/{group_id}")
|
|
61
|
+
async def read_group(
|
|
62
|
+
group_id: int,
|
|
63
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
64
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
65
|
+
) -> ReadGroupSchema:
|
|
66
|
+
resource_role = await unit_of_work.group.get_group_permission(
|
|
67
|
+
user=current_user,
|
|
68
|
+
group_id=group_id,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
if resource_role == Permission.NONE:
|
|
72
|
+
raise GroupNotFoundError
|
|
73
|
+
|
|
74
|
+
group = await unit_of_work.group.read_by_id(group_id=group_id)
|
|
75
|
+
return ReadGroupSchema.from_orm(group)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@router.patch("/groups/{group_id}")
|
|
79
|
+
async def update_group(
|
|
80
|
+
group_id: int,
|
|
81
|
+
group_data: UpdateGroupSchema,
|
|
82
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
83
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
84
|
+
) -> ReadGroupSchema:
|
|
85
|
+
resource_rule = await unit_of_work.group.get_group_permission(
|
|
86
|
+
user=current_user,
|
|
87
|
+
group_id=group_id,
|
|
88
|
+
)
|
|
89
|
+
if resource_rule == Permission.NONE:
|
|
90
|
+
raise GroupNotFoundError
|
|
91
|
+
|
|
92
|
+
if resource_rule < Permission.DELETE:
|
|
93
|
+
raise ActionNotAllowedError
|
|
94
|
+
|
|
95
|
+
async with unit_of_work:
|
|
96
|
+
group = await unit_of_work.group.update(
|
|
97
|
+
group_id=group_id,
|
|
98
|
+
owner_id=group_data.owner_id,
|
|
99
|
+
name=group_data.name,
|
|
100
|
+
description=group_data.description,
|
|
101
|
+
)
|
|
102
|
+
return ReadGroupSchema.from_orm(group)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@router.delete("/groups/{group_id}", dependencies=[Depends(get_user(is_superuser=True))])
|
|
106
|
+
async def delete_group(
|
|
107
|
+
group_id: int,
|
|
108
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
109
|
+
) -> StatusResponseSchema:
|
|
110
|
+
async with unit_of_work:
|
|
111
|
+
await unit_of_work.group.delete(group_id=group_id)
|
|
112
|
+
return StatusResponseSchema(ok=True, status_code=200, message="Group was deleted")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@router.get("/groups/{group_id}/users")
|
|
116
|
+
async def read_group_users(
|
|
117
|
+
group_id: int,
|
|
118
|
+
page: int = Query(gt=0, default=1),
|
|
119
|
+
page_size: int = Query(gt=0, le=200, default=20),
|
|
120
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
121
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
122
|
+
) -> UserPageSchemaAsGroupMember:
|
|
123
|
+
resource_role = await unit_of_work.group.get_group_permission(
|
|
124
|
+
user=current_user,
|
|
125
|
+
group_id=group_id,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if resource_role == Permission.NONE:
|
|
129
|
+
raise GroupNotFoundError
|
|
130
|
+
|
|
131
|
+
pagination = await unit_of_work.group.get_member_paginate(
|
|
132
|
+
group_id=group_id,
|
|
133
|
+
page=page,
|
|
134
|
+
page_size=page_size,
|
|
135
|
+
)
|
|
136
|
+
return UserPageSchemaAsGroupMember.from_pagination(pagination=pagination)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@router.patch("/groups/{group_id}/users/{user_id}")
|
|
140
|
+
async def update_user_role_group(
|
|
141
|
+
group_id: int,
|
|
142
|
+
user_id: int,
|
|
143
|
+
update_user_data: AddUserSchema,
|
|
144
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
145
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
146
|
+
) -> AddUserSchema:
|
|
147
|
+
resource_rule = await unit_of_work.group.get_group_permission(
|
|
148
|
+
user=current_user,
|
|
149
|
+
group_id=group_id,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
if resource_rule == Permission.NONE:
|
|
153
|
+
raise GroupNotFoundError
|
|
154
|
+
|
|
155
|
+
if resource_rule < Permission.DELETE:
|
|
156
|
+
raise ActionNotAllowedError
|
|
157
|
+
|
|
158
|
+
async with unit_of_work:
|
|
159
|
+
result = await unit_of_work.group.update_member_role(
|
|
160
|
+
group_id=group_id,
|
|
161
|
+
user_id=user_id,
|
|
162
|
+
role=update_user_data.role,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return AddUserSchema.from_orm(result)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@router.post("/groups/{group_id}/users/{user_id}")
|
|
169
|
+
async def add_user_to_group(
|
|
170
|
+
group_id: int,
|
|
171
|
+
user_id: int,
|
|
172
|
+
add_user_data: AddUserSchema,
|
|
173
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
174
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
175
|
+
) -> StatusResponseSchema:
|
|
176
|
+
resource_rule = await unit_of_work.group.get_group_permission(
|
|
177
|
+
user=current_user,
|
|
178
|
+
group_id=group_id,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
if resource_rule == Permission.NONE:
|
|
182
|
+
raise GroupNotFoundError
|
|
183
|
+
|
|
184
|
+
if resource_rule < Permission.DELETE:
|
|
185
|
+
raise ActionNotAllowedError
|
|
186
|
+
|
|
187
|
+
async with unit_of_work:
|
|
188
|
+
await unit_of_work.group.add_user(
|
|
189
|
+
group_id=group_id,
|
|
190
|
+
new_user_id=user_id,
|
|
191
|
+
role=add_user_data.role,
|
|
192
|
+
)
|
|
193
|
+
return StatusResponseSchema(
|
|
194
|
+
ok=True,
|
|
195
|
+
status_code=200,
|
|
196
|
+
message="User was successfully added to group",
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@router.delete("/groups/{group_id}/users/{user_id}")
|
|
201
|
+
async def delete_user_from_group(
|
|
202
|
+
group_id: int,
|
|
203
|
+
user_id: int,
|
|
204
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
205
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
206
|
+
) -> StatusResponseSchema:
|
|
207
|
+
resource_rule = await unit_of_work.group.get_group_permission(
|
|
208
|
+
user=current_user,
|
|
209
|
+
group_id=group_id,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
if resource_rule == Permission.NONE:
|
|
213
|
+
raise GroupNotFoundError
|
|
214
|
+
|
|
215
|
+
# User can delete himself from the group
|
|
216
|
+
if user_id != current_user.id:
|
|
217
|
+
if resource_rule < Permission.DELETE:
|
|
218
|
+
raise ActionNotAllowedError
|
|
219
|
+
|
|
220
|
+
async with unit_of_work:
|
|
221
|
+
await unit_of_work.group.delete_user(
|
|
222
|
+
group_id=group_id,
|
|
223
|
+
target_user_id=user_id,
|
|
224
|
+
)
|
|
225
|
+
return StatusResponseSchema(ok=True, status_code=200, message="User was successfully removed from group")
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023-2024 MTS (Mobile Telesystems)
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
from fastapi import APIRouter, Depends, Query, status
|
|
4
|
+
|
|
5
|
+
from syncmaster.backend.api.deps import UnitOfWorkMarker
|
|
6
|
+
from syncmaster.backend.services import UnitOfWork, get_user
|
|
7
|
+
from syncmaster.db.models import User
|
|
8
|
+
from syncmaster.db.utils import Permission
|
|
9
|
+
from syncmaster.exceptions import ActionNotAllowedError
|
|
10
|
+
from syncmaster.exceptions.group import GroupNotFoundError
|
|
11
|
+
from syncmaster.exceptions.queue import QueueDeleteError, QueueNotFoundError
|
|
12
|
+
from syncmaster.schemas.v1.queue import (
|
|
13
|
+
CreateQueueSchema,
|
|
14
|
+
QueuePageSchema,
|
|
15
|
+
ReadQueueSchema,
|
|
16
|
+
UpdateQueueSchema,
|
|
17
|
+
)
|
|
18
|
+
from syncmaster.schemas.v1.status import StatusResponseSchema
|
|
19
|
+
|
|
20
|
+
router = APIRouter(tags=["Queues"])
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@router.get("/queues/{queue_id}", description="Read queue by id")
|
|
24
|
+
async def read_queue(
|
|
25
|
+
queue_id: int,
|
|
26
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
27
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
28
|
+
) -> ReadQueueSchema:
|
|
29
|
+
resource_role = await unit_of_work.queue.get_resource_permission(
|
|
30
|
+
user=current_user,
|
|
31
|
+
resource_id=queue_id,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
if resource_role == Permission.NONE:
|
|
35
|
+
raise QueueNotFoundError
|
|
36
|
+
|
|
37
|
+
queue = await unit_of_work.queue.read_by_id(
|
|
38
|
+
queue_id=queue_id,
|
|
39
|
+
)
|
|
40
|
+
return ReadQueueSchema.from_orm(queue)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@router.post("/queues", description="Create new queue")
|
|
44
|
+
async def create_queue(
|
|
45
|
+
queue_data: CreateQueueSchema,
|
|
46
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
47
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
48
|
+
) -> ReadQueueSchema:
|
|
49
|
+
group_permission = await unit_of_work.queue.get_group_permission(
|
|
50
|
+
user=current_user,
|
|
51
|
+
group_id=queue_data.group_id,
|
|
52
|
+
)
|
|
53
|
+
if group_permission == Permission.NONE:
|
|
54
|
+
raise GroupNotFoundError
|
|
55
|
+
|
|
56
|
+
if group_permission < Permission.DELETE:
|
|
57
|
+
raise ActionNotAllowedError
|
|
58
|
+
|
|
59
|
+
async with unit_of_work:
|
|
60
|
+
queue = await unit_of_work.queue.create(queue_data.dict())
|
|
61
|
+
|
|
62
|
+
return ReadQueueSchema.from_orm(queue)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@router.patch("/queues/{queue_id}", description="Updating queue information")
|
|
66
|
+
async def update_queue(
|
|
67
|
+
queue_id: int,
|
|
68
|
+
queue_data: UpdateQueueSchema,
|
|
69
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
70
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
71
|
+
) -> ReadQueueSchema:
|
|
72
|
+
resource_role = await unit_of_work.queue.get_resource_permission(
|
|
73
|
+
user=current_user,
|
|
74
|
+
resource_id=queue_id,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
if resource_role == Permission.NONE:
|
|
78
|
+
raise QueueNotFoundError
|
|
79
|
+
|
|
80
|
+
if resource_role < Permission.DELETE:
|
|
81
|
+
raise ActionNotAllowedError
|
|
82
|
+
|
|
83
|
+
async with unit_of_work:
|
|
84
|
+
queue = await unit_of_work.queue.update(
|
|
85
|
+
queue_id=queue_id,
|
|
86
|
+
queue_data=queue_data,
|
|
87
|
+
)
|
|
88
|
+
return ReadQueueSchema.from_orm(queue)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@router.delete("/queues/{queue_id}", description="Delete queue by id")
|
|
92
|
+
async def delete_queue(
|
|
93
|
+
queue_id: int,
|
|
94
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
95
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
96
|
+
) -> StatusResponseSchema:
|
|
97
|
+
resource_role = await unit_of_work.queue.get_resource_permission(
|
|
98
|
+
user=current_user,
|
|
99
|
+
resource_id=queue_id,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if resource_role == Permission.NONE:
|
|
103
|
+
raise QueueNotFoundError
|
|
104
|
+
|
|
105
|
+
if resource_role < Permission.DELETE:
|
|
106
|
+
raise ActionNotAllowedError
|
|
107
|
+
|
|
108
|
+
queue = await unit_of_work.queue.read_by_id(queue_id=queue_id)
|
|
109
|
+
|
|
110
|
+
transfers = queue.transfers
|
|
111
|
+
|
|
112
|
+
if transfers:
|
|
113
|
+
raise QueueDeleteError(
|
|
114
|
+
f"The queue has an associated transfers(s). Number of the linked transfers: {len(transfers)}",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
async with unit_of_work:
|
|
118
|
+
await unit_of_work.queue.delete(queue_id=queue_id)
|
|
119
|
+
|
|
120
|
+
return StatusResponseSchema(
|
|
121
|
+
ok=True,
|
|
122
|
+
status_code=status.HTTP_200_OK,
|
|
123
|
+
message="Queue was deleted",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@router.get("/queues", description="Queues in page format")
|
|
128
|
+
async def read_queues(
|
|
129
|
+
group_id: int,
|
|
130
|
+
page: int = Query(gt=0, default=1),
|
|
131
|
+
page_size: int = Query(gt=0, le=200, default=20),
|
|
132
|
+
current_user: User = Depends(get_user(is_active=True)),
|
|
133
|
+
unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
|
|
134
|
+
) -> QueuePageSchema:
|
|
135
|
+
resource_role = await unit_of_work.queue.get_group_permission(
|
|
136
|
+
user=current_user,
|
|
137
|
+
group_id=group_id,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
if resource_role == Permission.NONE:
|
|
141
|
+
raise GroupNotFoundError
|
|
142
|
+
|
|
143
|
+
pagination = await unit_of_work.queue.paginate(
|
|
144
|
+
page=page,
|
|
145
|
+
page_size=page_size,
|
|
146
|
+
group_id=group_id,
|
|
147
|
+
)
|
|
148
|
+
return QueuePageSchema.from_pagination(pagination=pagination)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023-2024 MTS (Mobile Telesystems)
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
from fastapi import APIRouter
|
|
4
|
+
|
|
5
|
+
from syncmaster.backend.api.v1.auth.router import router as auth_router
|
|
6
|
+
from syncmaster.backend.api.v1.connections import router as connection_router
|
|
7
|
+
from syncmaster.backend.api.v1.groups import router as group_router
|
|
8
|
+
from syncmaster.backend.api.v1.queue import router as queue_router
|
|
9
|
+
from syncmaster.backend.api.v1.transfers.router import router as transfer_router
|
|
10
|
+
from syncmaster.backend.api.v1.users import router as user_router
|
|
11
|
+
|
|
12
|
+
router = APIRouter(prefix="/v1")
|
|
13
|
+
router.include_router(user_router)
|
|
14
|
+
router.include_router(auth_router)
|
|
15
|
+
router.include_router(group_router)
|
|
16
|
+
router.include_router(connection_router)
|
|
17
|
+
router.include_router(transfer_router)
|
|
18
|
+
router.include_router(queue_router)
|