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.
Files changed (110) hide show
  1. data_syncmaster-0.1.1.dist-info/LICENSE.txt +203 -0
  2. data_syncmaster-0.1.1.dist-info/METADATA +115 -0
  3. data_syncmaster-0.1.1.dist-info/RECORD +110 -0
  4. data_syncmaster-0.1.1.dist-info/WHEEL +4 -0
  5. syncmaster/__init__.py +6 -0
  6. syncmaster/backend/__init__.py +2 -0
  7. syncmaster/backend/api/__init__.py +2 -0
  8. syncmaster/backend/api/deps.py +20 -0
  9. syncmaster/backend/api/monitoring.py +10 -0
  10. syncmaster/backend/api/router.py +10 -0
  11. syncmaster/backend/api/v1/__init__.py +2 -0
  12. syncmaster/backend/api/v1/auth/__init__.py +2 -0
  13. syncmaster/backend/api/v1/auth/router.py +32 -0
  14. syncmaster/backend/api/v1/auth/utils.py +26 -0
  15. syncmaster/backend/api/v1/connections.py +300 -0
  16. syncmaster/backend/api/v1/groups.py +225 -0
  17. syncmaster/backend/api/v1/queue.py +148 -0
  18. syncmaster/backend/api/v1/router.py +18 -0
  19. syncmaster/backend/api/v1/transfers/__init__.py +2 -0
  20. syncmaster/backend/api/v1/transfers/router.py +469 -0
  21. syncmaster/backend/api/v1/transfers/utils.py +17 -0
  22. syncmaster/backend/api/v1/users.py +75 -0
  23. syncmaster/backend/export_openapi_schema.py +26 -0
  24. syncmaster/backend/handler.py +203 -0
  25. syncmaster/backend/logger.py +2 -0
  26. syncmaster/backend/main.py +63 -0
  27. syncmaster/backend/pre_start.py +94 -0
  28. syncmaster/backend/services/__init__.py +4 -0
  29. syncmaster/backend/services/auth.py +58 -0
  30. syncmaster/backend/services/unit_of_work.py +44 -0
  31. syncmaster/config.py +110 -0
  32. syncmaster/db/__init__.py +2 -0
  33. syncmaster/db/alembic.ini +41 -0
  34. syncmaster/db/base.py +28 -0
  35. syncmaster/db/factory.py +37 -0
  36. syncmaster/db/migrations/README +1 -0
  37. syncmaster/db/migrations/__init__.py +2 -0
  38. syncmaster/db/migrations/env.py +87 -0
  39. syncmaster/db/migrations/script.py.mako +24 -0
  40. syncmaster/db/migrations/versions/2023-11-23_478240cdad4b_init.py +242 -0
  41. syncmaster/db/migrations/versions/__init__.py +2 -0
  42. syncmaster/db/mixins.py +33 -0
  43. syncmaster/db/models.py +194 -0
  44. syncmaster/db/repositories/__init__.py +22 -0
  45. syncmaster/db/repositories/base.py +109 -0
  46. syncmaster/db/repositories/connection.py +138 -0
  47. syncmaster/db/repositories/credentials_repository.py +87 -0
  48. syncmaster/db/repositories/group.py +264 -0
  49. syncmaster/db/repositories/queue.py +195 -0
  50. syncmaster/db/repositories/repository_with_owner.py +115 -0
  51. syncmaster/db/repositories/run.py +78 -0
  52. syncmaster/db/repositories/transfer.py +202 -0
  53. syncmaster/db/repositories/user.py +72 -0
  54. syncmaster/db/repositories/utils.py +25 -0
  55. syncmaster/db/utils.py +31 -0
  56. syncmaster/dto/__init__.py +2 -0
  57. syncmaster/dto/connections.py +60 -0
  58. syncmaster/dto/transfers.py +46 -0
  59. syncmaster/exceptions/__init__.py +13 -0
  60. syncmaster/exceptions/base.py +12 -0
  61. syncmaster/exceptions/connection.py +28 -0
  62. syncmaster/exceptions/credentials.py +8 -0
  63. syncmaster/exceptions/group.py +27 -0
  64. syncmaster/exceptions/queue.py +16 -0
  65. syncmaster/exceptions/run.py +19 -0
  66. syncmaster/exceptions/transfer.py +39 -0
  67. syncmaster/exceptions/user.py +11 -0
  68. syncmaster/schemas/__init__.py +2 -0
  69. syncmaster/schemas/v1/__init__.py +54 -0
  70. syncmaster/schemas/v1/auth.py +12 -0
  71. syncmaster/schemas/v1/connection_types.py +9 -0
  72. syncmaster/schemas/v1/connections/__init__.py +2 -0
  73. syncmaster/schemas/v1/connections/connection.py +146 -0
  74. syncmaster/schemas/v1/connections/hdfs.py +40 -0
  75. syncmaster/schemas/v1/connections/hive.py +40 -0
  76. syncmaster/schemas/v1/connections/oracle.py +58 -0
  77. syncmaster/schemas/v1/connections/postgres.py +48 -0
  78. syncmaster/schemas/v1/connections/s3.py +66 -0
  79. syncmaster/schemas/v1/file_formats.py +7 -0
  80. syncmaster/schemas/v1/groups.py +39 -0
  81. syncmaster/schemas/v1/page.py +40 -0
  82. syncmaster/schemas/v1/queue.py +32 -0
  83. syncmaster/schemas/v1/status.py +16 -0
  84. syncmaster/schemas/v1/transfer_types.py +6 -0
  85. syncmaster/schemas/v1/transfers/__init__.py +172 -0
  86. syncmaster/schemas/v1/transfers/db.py +23 -0
  87. syncmaster/schemas/v1/transfers/file/__init__.py +2 -0
  88. syncmaster/schemas/v1/transfers/file/base.py +47 -0
  89. syncmaster/schemas/v1/transfers/file/hdfs.py +27 -0
  90. syncmaster/schemas/v1/transfers/file/s3.py +27 -0
  91. syncmaster/schemas/v1/transfers/file_format.py +29 -0
  92. syncmaster/schemas/v1/transfers/run.py +37 -0
  93. syncmaster/schemas/v1/transfers/strategy.py +15 -0
  94. syncmaster/schemas/v1/types.py +5 -0
  95. syncmaster/schemas/v1/users.py +83 -0
  96. syncmaster/worker/__init__.py +2 -0
  97. syncmaster/worker/base.py +14 -0
  98. syncmaster/worker/config.py +18 -0
  99. syncmaster/worker/controller.py +127 -0
  100. syncmaster/worker/handlers/__init__.py +2 -0
  101. syncmaster/worker/handlers/base.py +49 -0
  102. syncmaster/worker/handlers/file/__init__.py +2 -0
  103. syncmaster/worker/handlers/file/base.py +56 -0
  104. syncmaster/worker/handlers/file/hdfs.py +14 -0
  105. syncmaster/worker/handlers/file/s3.py +20 -0
  106. syncmaster/worker/handlers/hive.py +41 -0
  107. syncmaster/worker/handlers/oracle.py +48 -0
  108. syncmaster/worker/handlers/postgres.py +47 -0
  109. syncmaster/worker/spark.py +93 -0
  110. 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)
@@ -0,0 +1,2 @@
1
+ # SPDX-FileCopyrightText: 2023-2024 MTS (Mobile Telesystems)
2
+ # SPDX-License-Identifier: Apache-2.0