Skip to content

Commit d4ed874

Browse files
openapi specs
1 parent 1feb56a commit d4ed874

File tree

7 files changed

+247
-85
lines changed

7 files changed

+247
-85
lines changed

api/specs/web-server/_folders.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,27 @@ async def list_folders(
6363
...
6464

6565

66+
@router.get(
67+
"/folders:search",
68+
response_model=Envelope[list[FolderGet]],
69+
)
70+
async def list_folders_full_search(
71+
params: Annotated[PageQueryParameters, Depends()],
72+
order_by: Annotated[
73+
Json,
74+
Query(
75+
description="Order by field (modified_at|name|description) and direction (asc|desc). The default sorting order is ascending.",
76+
example='{"field": "name", "direction": "desc"}',
77+
),
78+
] = '{"field": "modified_at", "direction": "desc"}',
79+
filters: Annotated[
80+
Json | None,
81+
Query(description=FolderFilters.schema_json(indent=1)),
82+
] = None,
83+
):
84+
...
85+
86+
6687
@router.get(
6788
"/folders/{folder_id}",
6889
response_model=Envelope[FolderGet],

packages/models-library/src/models_library/folders.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from pydantic import BaseModel, Field, PositiveInt, validator
66

7+
from .access_rights import AccessRights
78
from .users import GroupID, UserID
89
from .utils.enums import StrAutoEnum
910
from .workspaces import WorkspaceID
@@ -66,3 +67,10 @@ class FolderDB(BaseModel):
6667

6768
class Config:
6869
orm_mode = True
70+
71+
72+
class UserFolderAccessRightsDB(FolderDB):
73+
my_access_rights: AccessRights
74+
75+
class Config:
76+
orm_mode = True

services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2690,6 +2690,70 @@ paths:
26902690
application/json:
26912691
schema:
26922692
$ref: '#/components/schemas/Envelope_FolderGet_'
2693+
/v0/folders:search:
2694+
get:
2695+
tags:
2696+
- folders
2697+
summary: List Folders Full Search
2698+
operationId: list_folders_full_search
2699+
parameters:
2700+
- description: Order by field (modified_at|name|description) and direction (asc|desc).
2701+
The default sorting order is ascending.
2702+
required: false
2703+
schema:
2704+
title: Order By
2705+
description: Order by field (modified_at|name|description) and direction
2706+
(asc|desc). The default sorting order is ascending.
2707+
default: '{"field": "modified_at", "direction": "desc"}'
2708+
example: '{"field": "name", "direction": "desc"}'
2709+
name: order_by
2710+
in: query
2711+
- description: "{\n \"title\": \"FolderFilters\",\n \"description\": \"Encoded\
2712+
\ as JSON. Each available filter can have its own logic (should be well\
2713+
\ documented)\\nInspired by Docker API https://docs.docker.com/engine/api/v1.43/#tag/Container/operation/ContainerList.\"\
2714+
,\n \"type\": \"object\",\n \"properties\": {\n \"trashed\": {\n \"title\"\
2715+
: \"Trashed\",\n \"description\": \"Set to true to list trashed, false\
2716+
\ to list non-trashed (default), None to list all\",\n \"default\": false,\n\
2717+
\ \"type\": \"boolean\"\n }\n }\n}"
2718+
required: false
2719+
schema:
2720+
title: Filters
2721+
type: string
2722+
description: "{\n \"title\": \"FolderFilters\",\n \"description\": \"Encoded\
2723+
\ as JSON. Each available filter can have its own logic (should be well\
2724+
\ documented)\\nInspired by Docker API https://docs.docker.com/engine/api/v1.43/#tag/Container/operation/ContainerList.\"\
2725+
,\n \"type\": \"object\",\n \"properties\": {\n \"trashed\": {\n \"\
2726+
title\": \"Trashed\",\n \"description\": \"Set to true to list trashed,\
2727+
\ false to list non-trashed (default), None to list all\",\n \"default\"\
2728+
: false,\n \"type\": \"boolean\"\n }\n }\n}"
2729+
format: json-string
2730+
name: filters
2731+
in: query
2732+
- required: false
2733+
schema:
2734+
title: Limit
2735+
exclusiveMaximum: true
2736+
minimum: 1
2737+
type: integer
2738+
default: 20
2739+
maximum: 50
2740+
name: limit
2741+
in: query
2742+
- required: false
2743+
schema:
2744+
title: Offset
2745+
minimum: 0
2746+
type: integer
2747+
default: 0
2748+
name: offset
2749+
in: query
2750+
responses:
2751+
'200':
2752+
description: Successful Response
2753+
content:
2754+
application/json:
2755+
schema:
2756+
$ref: '#/components/schemas/Envelope_list_models_library.api_schemas_webserver.folders_v2.FolderGet__'
26932757
/v0/folders/{folder_id}:
26942758
get:
26952759
tags:

services/web/server/src/simcore_service_webserver/folders/_folders_api.py

Lines changed: 53 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -153,46 +153,65 @@ async def list_folders(
153153
limit: int,
154154
order_by: OrderBy,
155155
) -> FolderGetPage:
156-
workspace_is_private = True
157-
user_folder_access_rights = AccessRights(read=True, write=True, delete=True)
156+
# NOTE: Folder access rights for listing are checked within the listing DB function.
158157

159-
if workspace_id:
160-
user_workspace_access_rights = await check_user_workspace_access(
161-
app,
162-
user_id=user_id,
163-
workspace_id=workspace_id,
164-
product_name=product_name,
165-
permission="read",
166-
)
167-
workspace_is_private = False
168-
user_folder_access_rights = user_workspace_access_rights.my_access_rights
169-
_workspace_query = WorkspaceQuery(
170-
workspace_scope=WorkspaceScope.SHARED, workspace_id=workspace_id
171-
)
172-
else:
173-
_workspace_query = WorkspaceQuery(workspace_scope=WorkspaceScope.PRIVATE)
158+
total_count, folders = await folders_db.list_(
159+
app,
160+
product_name=product_name,
161+
user_id=user_id,
162+
folder_query=(
163+
FolderQuery(folder_scope=FolderScope.SPECIFIC, folder_id=folder_id)
164+
if folder_id
165+
else FolderQuery(folder_scope=FolderScope.ROOT)
166+
),
167+
workspace_query=(
168+
WorkspaceQuery(
169+
workspace_scope=WorkspaceScope.SHARED, workspace_id=workspace_id
170+
)
171+
if workspace_id
172+
else WorkspaceQuery(workspace_scope=WorkspaceScope.PRIVATE)
173+
),
174+
filter_trashed=trashed,
175+
offset=offset,
176+
limit=limit,
177+
order_by=order_by,
178+
)
179+
return FolderGetPage(
180+
items=[
181+
FolderGet(
182+
folder_id=folder.folder_id,
183+
parent_folder_id=folder.parent_folder_id,
184+
name=folder.name,
185+
created_at=folder.created,
186+
modified_at=folder.modified,
187+
trashed_at=folder.trashed_at,
188+
owner=folder.created_by_gid,
189+
workspace_id=folder.workspace_id,
190+
my_access_rights=folder.my_access_rights,
191+
)
192+
for folder in folders
193+
],
194+
total=total_count,
195+
)
174196

175-
if folder_id:
176-
# Check user access to folder
177-
await folders_db.get_for_user_or_workspace(
178-
app,
179-
folder_id=folder_id,
180-
product_name=product_name,
181-
user_id=user_id if workspace_is_private else None,
182-
workspace_id=workspace_id,
183-
)
184-
_folder_query = FolderQuery(
185-
folder_scope=FolderScope.SPECIFIC, folder_id=folder_id
186-
)
187-
else:
188-
_folder_query = FolderQuery(folder_scope=FolderScope.ROOT)
197+
198+
async def list_folders_full_search(
199+
app: web.Application,
200+
user_id: UserID,
201+
product_name: ProductName,
202+
trashed: bool | None,
203+
offset: NonNegativeInt,
204+
limit: int,
205+
order_by: OrderBy,
206+
) -> FolderGetPage:
207+
# NOTE: Folder access rights for listing are checked within the listing DB function.
189208

190209
total_count, folders = await folders_db.list_(
191210
app,
192211
product_name=product_name,
193212
user_id=user_id,
194-
folder_query=_folder_query,
195-
workspace_query=_workspace_query,
213+
folder_query=FolderQuery(folder_scope=FolderScope.ALL),
214+
workspace_query=WorkspaceQuery(workspace_scope=WorkspaceScope.ALL),
196215
filter_trashed=trashed,
197216
offset=offset,
198217
limit=limit,
@@ -209,7 +228,7 @@ async def list_folders(
209228
trashed_at=folder.trashed_at,
210229
owner=folder.created_by_gid,
211230
workspace_id=folder.workspace_id,
212-
my_access_rights=user_folder_access_rights,
231+
my_access_rights=folder.my_access_rights,
213232
)
214233
for folder in folders
215234
],

services/web/server/src/simcore_service_webserver/folders/_folders_db.py

Lines changed: 36 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@
1010

1111
import sqlalchemy as sa
1212
from aiohttp import web
13-
from models_library.folders import FolderDB, FolderID, FolderQuery, FolderScope
13+
from models_library.folders import (
14+
FolderDB,
15+
FolderID,
16+
FolderQuery,
17+
FolderScope,
18+
UserFolderAccessRightsDB,
19+
)
1420
from models_library.products import ProductName
1521
from models_library.projects import ProjectID
1622
from models_library.rest_ordering import OrderBy, OrderDirection
@@ -20,21 +26,17 @@
2026
from simcore_postgres_database.models.folders_v2 import folders_v2
2127
from simcore_postgres_database.models.projects import projects
2228
from simcore_postgres_database.models.projects_to_folders import projects_to_folders
23-
from simcore_postgres_database.models.workspaces_access_rights import (
24-
workspaces_access_rights,
25-
)
2629
from simcore_postgres_database.utils_repos import (
2730
pass_or_acquire_connection,
2831
transaction_context,
2932
)
30-
from simcore_postgres_database.utils_sql import assemble_array_groups
3133
from sqlalchemy import func
3234
from sqlalchemy.ext.asyncio import AsyncConnection
3335
from sqlalchemy.orm import aliased
3436
from sqlalchemy.sql import ColumnElement, CompoundSelect, Select, asc, desc, select
3537

3638
from ..db.plugin import get_asyncpg_engine
37-
from ..groups.api import list_all_user_groups
39+
from ..workspaces._workspaces_db import _create_my_access_rights_subquery
3840
from .errors import FolderAccessForbiddenError, FolderNotFoundError
3941

4042
_logger = logging.getLogger(__name__)
@@ -63,6 +65,10 @@ def as_dict_exclude_unset(**params) -> dict[str, Any]:
6365
folders_v2.c.workspace_id,
6466
)
6567

68+
# _SELECTION_ARGS_WITH_MY_ACCESS_RIGHTS = (
69+
# _SELECTION_ARGS,
70+
# )
71+
6672

6773
async def create(
6874
app: web.Application,
@@ -114,32 +120,15 @@ async def list_( # pylint: disable=too-many-arguments,too-many-branches
114120
limit: int,
115121
# order
116122
order_by: OrderBy,
117-
) -> tuple[int, list[FolderDB]]:
123+
) -> tuple[int, list[UserFolderAccessRightsDB]]:
118124
"""
119-
Assumptions -
120-
121125
folder_query - Used to filter in which folder we want to list folders.
122126
trashed - If set to true, it returns folders **explicitly** trashed, if false then non-trashed folders.
123127
"""
124128

125-
workspace_access_rights_subquery = (
126-
sa.select(
127-
workspaces_access_rights.c.workspace_id,
128-
sa.func.jsonb_object_agg(
129-
workspaces_access_rights.c.gid,
130-
sa.func.jsonb_build_object(
131-
"read",
132-
workspaces_access_rights.c.read,
133-
"write",
134-
workspaces_access_rights.c.write,
135-
"delete",
136-
workspaces_access_rights.c.delete,
137-
),
138-
)
139-
.filter(workspaces_access_rights.c.read)
140-
.label("access_rights"),
141-
).group_by(workspaces_access_rights.c.workspace_id)
142-
).subquery("workspace_access_rights_subquery")
129+
workspace_access_rights_subquery = _create_my_access_rights_subquery(
130+
user_id=user_id
131+
)
143132

144133
if workspace_query.workspace_scope is not WorkspaceScope.SHARED:
145134
assert workspace_query.workspace_scope in ( # nosec
@@ -148,7 +137,17 @@ async def list_( # pylint: disable=too-many-arguments,too-many-branches
148137
)
149138

150139
private_workspace_query = (
151-
select(*_SELECTION_ARGS)
140+
select(
141+
*_SELECTION_ARGS,
142+
func.json_build_object(
143+
"read",
144+
sa.text("true"),
145+
"write",
146+
sa.text("true"),
147+
"delete",
148+
sa.text("true"),
149+
).label("my_access_rights"),
150+
)
152151
.select_from(folders_v2)
153152
.where(
154153
(folders_v2.c.product_name == product_name)
@@ -163,11 +162,13 @@ async def list_( # pylint: disable=too-many-arguments,too-many-branches
163162
WorkspaceScope.SHARED,
164163
WorkspaceScope.ALL,
165164
)
166-
_user_groups = await list_all_user_groups(app, user_id=user_id)
167-
_user_groups_ids = [group.gid for group in _user_groups]
165+
# _user_groups = await list_all_user_groups(app, user_id=user_id)
166+
# _user_groups_ids = [group.gid for group in _user_groups]
168167

169168
shared_workspace_query = (
170-
select(*_SELECTION_ARGS)
169+
select(
170+
*_SELECTION_ARGS, workspace_access_rights_subquery.c.my_access_rights
171+
)
171172
.select_from(
172173
folders_v2.join(
173174
workspace_access_rights_subquery,
@@ -177,14 +178,7 @@ async def list_( # pylint: disable=too-many-arguments,too-many-branches
177178
)
178179
.where(
179180
(folders_v2.c.product_name == product_name)
180-
& (
181-
folders_v2.c.user_id.is_(None)
182-
& (
183-
sa.text(
184-
f"jsonb_exists_any(workspace_access_rights_subquery.access_rights, {assemble_array_groups(_user_groups_ids)})"
185-
)
186-
)
187-
)
181+
& (folders_v2.c.user_id.is_(None))
188182
)
189183
)
190184
else:
@@ -245,7 +239,9 @@ async def list_( # pylint: disable=too-many-arguments,too-many-branches
245239
total_count = await conn.scalar(count_query)
246240

247241
result = await conn.stream(list_query)
248-
folders: list[FolderDB] = [FolderDB.from_orm(row) async for row in result]
242+
folders: list[UserFolderAccessRightsDB] = [
243+
UserFolderAccessRightsDB.from_orm(row) async for row in result
244+
]
249245
return cast(int, total_count), folders
250246

251247

0 commit comments

Comments
 (0)