Skip to content

Commit ee1efc7

Browse files
committed
drafted service and rest
1 parent bce44c3 commit ee1efc7

File tree

5 files changed

+134
-57
lines changed

5 files changed

+134
-57
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from typing_extensions import TypedDict
2+
3+
4+
class AccessRightsDict(TypedDict):
5+
read: bool
6+
write: bool
7+
delete: bool

packages/postgres-database/src/simcore_postgres_database/utils_tags.py

Lines changed: 12 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -234,54 +234,43 @@ async def delete(
234234
# ACCESS RIGHTS
235235
#
236236

237-
async def _has_access_rights(
237+
async def has_access_rights(
238238
self,
239-
connection: AsyncConnection,
239+
connection: AsyncConnection | None = None,
240240
*,
241241
caller_id: int,
242242
tag_id: int,
243243
read: bool = False,
244244
write: bool = False,
245245
delete: bool = False,
246246
) -> bool:
247-
result = await connection.execute(
248-
has_access_rights_stmt(
249-
tag_id=tag_id,
250-
caller_user_id=caller_id,
251-
read=read,
252-
write=write,
253-
delete=delete,
247+
async with pass_or_acquire_connection(self.engine, connection) as conn:
248+
result = await conn.execute(
249+
has_access_rights_stmt(
250+
tag_id=tag_id,
251+
caller_user_id=caller_id,
252+
read=read,
253+
write=write,
254+
delete=delete,
255+
)
254256
)
255-
)
256-
return result.fetchone() is not None
257+
return result.fetchone() is not None
257258

258259
async def list_access_rights(
259260
self,
260261
connection: AsyncConnection | None = None,
261262
*,
262-
# caller
263-
user_id: int,
264263
# target
265264
tag_id: int,
266265
):
267266
async with pass_or_acquire_connection(self.engine, connection) as conn:
268-
if not await self._has_access_rights(
269-
conn, caller_id=user_id, tag_id=tag_id, read=True
270-
):
271-
# TODO: empyt list or error?
272-
raise TagOperationNotAllowedError(
273-
operation="share.list", user_id=user_id, tag_id=tag_id
274-
)
275-
276267
result = await conn.execute(list_tag_group_access_stmt(tag_id=tag_id))
277268
return [dict(row) for row in result.fetchall()]
278269

279270
async def create_or_update_access_rights(
280271
self,
281272
connection: AsyncConnection | None = None,
282273
*,
283-
# caller
284-
user_id: int,
285274
# target
286275
tag_id: int,
287276
group_id: int,
@@ -291,13 +280,6 @@ async def create_or_update_access_rights(
291280
delete: bool,
292281
):
293282
async with transaction_context(self.engine, connection) as conn:
294-
if not await self._has_access_rights(
295-
conn, caller_id=user_id, tag_id=tag_id, write=True
296-
):
297-
raise TagOperationNotAllowedError(
298-
operation="share.write", user_id=user_id, tag_id=tag_id
299-
)
300-
301283
result = await conn.execute(
302284
upsert_tags_access_rights_stmt(
303285
tag_id=tag_id,
@@ -314,19 +296,11 @@ async def delete_access_rights(
314296
self,
315297
connection: AsyncConnection | None = None,
316298
*,
317-
user_id: int,
318299
# target
319300
tag_id: int,
320301
group_id: int,
321302
) -> bool:
322303
async with transaction_context(self.engine, connection) as conn:
323-
if not await self._has_access_rights(
324-
conn, caller_id=user_id, tag_id=tag_id, write=True
325-
):
326-
raise TagOperationNotAllowedError(
327-
operation="share.delete", user_id=user_id, tag_id=tag_id
328-
)
329-
330304
deleted: bool = await conn.scalar(
331305
delete_tag_access_rights_stmt(tag_id=tag_id, group_id=group_id)
332306
)

services/web/server/src/simcore_service_webserver/tags/_rest.py

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
from aiohttp import web
2-
from pydantic import TypeAdapter
32
from servicelib.aiohttp import status
43
from servicelib.aiohttp.requests_validation import (
54
parse_request_body_as,
65
parse_request_path_parameters_as,
76
)
8-
from servicelib.mimetype_constants import MIMETYPE_APPLICATION_JSON
97
from simcore_postgres_database.utils_tags import (
108
TagNotFoundError,
119
TagOperationNotAllowedError,
@@ -123,49 +121,72 @@ async def delete_tag(request: web.Request):
123121
@permission_required("tag.crud.*")
124122
@_handle_tags_exceptions
125123
async def list_tag_groups(request: web.Request):
124+
req_ctx = TagRequestContext.model_validate(request)
126125
path_params = parse_request_path_parameters_as(TagPathParams, request)
127126

128-
assert path_params # nosec
129-
assert envelope_json_response(TypeAdapter(list[TagGroupGet]).validate_python([]))
130-
131-
raise NotImplementedError
127+
got = await _service.list_tag_groups(
128+
request.app,
129+
caller_user_id=req_ctx.user_id,
130+
tag_id=path_params.tag_id,
131+
)
132+
return envelope_json_response([TagGroupGet.from_model(md) for md in got])
132133

133134

134135
@routes.post(f"/{VTAG}/tags/{{tag_id}}/groups/{{group_id}}", name="create_tag_group")
135136
@login_required
136137
@permission_required("tag.crud.*")
137138
@_handle_tags_exceptions
138139
async def create_tag_group(request: web.Request):
140+
req_ctx = TagRequestContext.model_validate(request)
139141
path_params = parse_request_path_parameters_as(TagGroupPathParams, request)
140-
new_tag_group = await parse_request_body_as(TagGroupCreate, request)
142+
body_params = await parse_request_body_as(TagGroupCreate, request)
141143

142-
assert path_params # nosec
143-
assert new_tag_group # nosec
144+
got = await _service.share_tag_with_group(
145+
request.app,
146+
caller_user_id=req_ctx.user_id,
147+
tag_id=path_params.tag_id,
148+
group_id=path_params.group_id,
149+
access_rights=body_params.to_model(),
150+
)
144151

145-
raise NotImplementedError
152+
return envelope_json_response(
153+
TagGroupGet.from_model(got), status_cls=web.HTTPCreated
154+
)
146155

147156

148157
@routes.put(f"/{VTAG}/tags/{{tag_id}}/groups/{{group_id}}", name="replace_tag_group")
149158
@login_required
150159
@permission_required("tag.crud.*")
151160
@_handle_tags_exceptions
152161
async def replace_tag_group(request: web.Request):
162+
req_ctx = TagRequestContext.model_validate(request)
153163
path_params = parse_request_path_parameters_as(TagGroupPathParams, request)
154-
new_tag_group = await parse_request_body_as(TagGroupCreate, request)
164+
body_params = await parse_request_body_as(TagGroupCreate, request)
155165

156-
assert path_params # nosec
157-
assert new_tag_group # nosec
166+
got = await _service.share_tag_with_group(
167+
request.app,
168+
caller_user_id=req_ctx.user_id,
169+
tag_id=path_params.tag_id,
170+
group_id=path_params.group_id,
171+
access_rights=body_params.to_model(),
172+
)
158173

159-
raise NotImplementedError
174+
return envelope_json_response(TagGroupGet.from_model(got))
160175

161176

162177
@routes.delete(f"/{VTAG}/tags/{{tag_id}}/groups/{{group_id}}", name="delete_tag_group")
163178
@login_required
164179
@permission_required("tag.crud.*")
165180
@_handle_tags_exceptions
166181
async def delete_tag_group(request: web.Request):
182+
req_ctx = TagRequestContext.model_validate(request)
167183
path_params = parse_request_path_parameters_as(TagGroupPathParams, request)
168184

169-
assert path_params # nosec
170-
assert web.HTTPNoContent(content_type=MIMETYPE_APPLICATION_JSON)
171-
raise NotImplementedError
185+
await _service.unshare_tag_with_group(
186+
request.app,
187+
caller_user_id=req_ctx.user_id,
188+
tag_id=path_params.tag_id,
189+
group_id=path_params.group_id,
190+
)
191+
192+
return web.json_response(status=status.HTTP_204_NO_CONTENT)

services/web/server/src/simcore_service_webserver/tags/_service.py

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
"""
33

44
from aiohttp import web
5+
from common_library.groups_dicts import AccessRightsDict
56
from models_library.basic_types import IdInt
7+
from models_library.groups import GroupID
68
from models_library.users import UserID
79
from servicelib.aiohttp.db_asyncpg_engine import get_async_engine
8-
from simcore_postgres_database.utils_tags import TagsRepo
10+
from simcore_postgres_database.utils_tags import TagOperationNotAllowedError, TagsRepo
911
from sqlalchemy.ext.asyncio import AsyncEngine
1012

1113
from .schemas import TagCreate, TagGet, TagUpdate
@@ -56,3 +58,68 @@ async def delete_tag(app: web.Application, user_id: UserID, tag_id: IdInt):
5658

5759
repo = TagsRepo(engine)
5860
await repo.delete(user_id=user_id, tag_id=tag_id)
61+
62+
63+
async def share_tag_with_group(
64+
app: web.Application,
65+
*,
66+
caller_user_id: UserID,
67+
tag_id: IdInt,
68+
group_id: GroupID,
69+
access_rights: AccessRightsDict,
70+
):
71+
"""
72+
Raises:
73+
TagOperationNotAllowedError
74+
"""
75+
repo = TagsRepo(get_async_engine(app))
76+
77+
if not await repo.has_access_rights(
78+
caller_id=caller_user_id, tag_id=tag_id, write=True
79+
):
80+
raise TagOperationNotAllowedError(
81+
operation="share.write", user_id=caller_user_id, tag_id=tag_id
82+
)
83+
84+
await repo.create_or_update_access_rights(
85+
tag_id=tag_id,
86+
group_id=group_id,
87+
**access_rights,
88+
)
89+
90+
91+
async def unshare_tag_with_group(
92+
app: web.Application,
93+
*,
94+
caller_user_id: UserID,
95+
tag_id: IdInt,
96+
group_id: GroupID,
97+
) -> bool:
98+
repo = TagsRepo(get_async_engine(app))
99+
100+
if not await repo.has_access_rights(
101+
caller_id=caller_user_id, tag_id=tag_id, delete=True
102+
):
103+
raise TagOperationNotAllowedError(
104+
operation="share.delete", user_id=caller_user_id, tag_id=tag_id
105+
)
106+
107+
deleted: bool = await repo.delete_access_rights(tag_id=tag_id, group_id=group_id)
108+
return deleted
109+
110+
111+
async def list_tag_groups(
112+
app: web.Application,
113+
*,
114+
caller_user_id: UserID,
115+
tag_id: IdInt,
116+
):
117+
118+
repo = TagsRepo(get_async_engine(app))
119+
120+
if not await repo.has_access_rights(
121+
caller_id=caller_user_id, tag_id=tag_id, read=True
122+
):
123+
return []
124+
125+
return await repo.list_access_rights(tag_id=tag_id)

services/web/server/src/simcore_service_webserver/tags/schemas.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import re
22
from datetime import datetime
3-
from typing import Annotated
3+
from typing import Annotated, Any, Self
44

5+
from common_library.groups_dicts import AccessRightsDict
56
from models_library.api_schemas_webserver._base import InputSchema, OutputSchema
67
from models_library.groups import GroupID
78
from models_library.rest_base import RequestParameters, StrictRequestParameters
@@ -84,6 +85,9 @@ class TagGroupCreate(InputSchema):
8485
write: bool
8586
delete: bool
8687

88+
def to_model(self) -> AccessRightsDict:
89+
return AccessRightsDict(**self.model_dump())
90+
8791

8892
class TagGroupGet(OutputSchema):
8993
gid: GroupID
@@ -94,3 +98,7 @@ class TagGroupGet(OutputSchema):
9498
# timestamps
9599
created: datetime
96100
modified: datetime
101+
102+
@classmethod
103+
def from_model(cls, data: dict[str, Any]) -> Self:
104+
raise NotImplementedError

0 commit comments

Comments
 (0)