Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🎨 Improve worskpace feature (1. Part) #6303

Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env-devel
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ AUTOSCALING_NODES_MONITORING=null
AUTOSCALING_POLL_INTERVAL=10
AUTOSCALING_SSM_ACCESS=null

AWS_S3_CLI_S3='{"S3_ACCESS_KEY":"12345678", "S3_BUCKET_NAME":"simcore", "S3_ENDPOINT": "http://172.17.0.1:9001", "S3_SECRET_KEY": "12345678", "S3_REGION": "us-east-1"}'
AWS_S3_CLI_S3=null

CATALOG_BACKGROUND_TASK_REST_TIME=60
CATALOG_DEV_FEATURES_ENABLED=0
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from datetime import datetime
from typing import NamedTuple

from models_library.access_rights import AccessRights
from models_library.basic_types import IDStr
from models_library.folders import FolderID
from models_library.users import GroupID
Expand All @@ -18,6 +19,8 @@ class FolderGet(OutputSchema):
created_at: datetime
modified_at: datetime
owner: GroupID
workspace_id: WorkspaceID | None
my_access_rights: AccessRights


class FolderGetPage(NamedTuple):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from typing import Any, Literal, TypeAlias

from models_library.folders import FolderID
from models_library.workspaces import WorkspaceID
from pydantic import Field, validator

Expand All @@ -24,6 +25,7 @@
from ..utils.common_validators import (
empty_str_to_none_pre_validator,
none_to_empty_str_pre_validator,
null_or_none_str_to_none_validator,
)
from ..utils.pydantic_tools_extension import FieldNotRequired
from ._base import EmptyModel, InputSchema, OutputSchema
Expand All @@ -41,11 +43,16 @@ class ProjectCreateNew(InputSchema):
classifiers: list[ClassifierID] = Field(default_factory=list)
ui: StudyUI | None = None
workspace_id: WorkspaceID | None = None
folder_id: FolderID | None = None

_empty_is_none = validator(
"uuid", "thumbnail", "description", "workspace_id", allow_reuse=True, pre=True
"uuid", "thumbnail", "description", allow_reuse=True, pre=True
)(empty_str_to_none_pre_validator)

_null_or_none_to_none = validator(
"workspace_id", "folder_id", allow_reuse=True, pre=True
)(null_or_none_str_to_none_validator)


# NOTE: based on OVERRIDABLE_DOCUMENT_KEYS
class ProjectCopyOverride(InputSchema):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8031,6 +8031,7 @@ components:
- createdAt
- modifiedAt
- owner
- myAccessRights
type: object
properties:
folderId:
Expand Down Expand Up @@ -8059,6 +8060,13 @@ components:
exclusiveMinimum: true
type: integer
minimum: 0
workspaceId:
title: Workspaceid
exclusiveMinimum: true
type: integer
minimum: 0
myAccessRights:
$ref: '#/components/schemas/models_library__access_rights__AccessRights'
GenerateInvitation:
title: GenerateInvitation
required:
Expand Down Expand Up @@ -8390,7 +8398,6 @@ components:
issuer:
title: Issuer
type: string
format: email
guest:
title: Guest
type: string
Expand Down Expand Up @@ -9825,6 +9832,11 @@ components:
exclusiveMinimum: true
type: integer
minimum: 0
folderId:
title: Folderid
exclusiveMinimum: true
type: integer
minimum: 0
ProjectGet:
title: ProjectGet
required:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import logging

from aiohttp import web
from models_library.access_rights import AccessRights
from models_library.api_schemas_webserver.folders_v2 import FolderGet, FolderGetPage
from models_library.folders import FolderID
from models_library.products import ProductName
Expand Down Expand Up @@ -35,15 +36,17 @@ async def create_folder(
user = await get_user(app, user_id=user_id)

workspace_is_private = True
user_folder_access_rights = AccessRights(read=True, write=True, delete=True)
if workspace_id:
await check_user_workspace_access(
user_workspace_access_rights = await check_user_workspace_access(
app,
user_id=user_id,
workspace_id=workspace_id,
product_name=product_name,
permission="write",
)
workspace_is_private = False
user_folder_access_rights = user_workspace_access_rights.my_access_rights

# Check parent_folder_id lives in the workspace
if parent_folder_id:
Expand Down Expand Up @@ -86,6 +89,8 @@ async def create_folder(
created_at=folder_db.created,
modified_at=folder_db.modified,
owner=folder_db.created_by_gid,
workspace_id=workspace_id,
my_access_rights=user_folder_access_rights,
)


Expand All @@ -100,15 +105,17 @@ async def get_folder(
)

workspace_is_private = True
user_folder_access_rights = AccessRights(read=True, write=True, delete=True)
if folder_db.workspace_id:
await check_user_workspace_access(
user_workspace_access_rights = await check_user_workspace_access(
app,
user_id=user_id,
workspace_id=folder_db.workspace_id,
product_name=product_name,
permission="read",
)
workspace_is_private = False
user_folder_access_rights = user_workspace_access_rights.my_access_rights

folder_db = await folders_db.get_for_user_or_workspace(
app,
Expand All @@ -124,6 +131,8 @@ async def get_folder(
created_at=folder_db.created,
modified_at=folder_db.modified,
owner=folder_db.created_by_gid,
workspace_id=folder_db.workspace_id,
my_access_rights=user_folder_access_rights,
)


Expand All @@ -138,16 +147,18 @@ async def list_folders(
order_by: OrderBy,
) -> FolderGetPage:
workspace_is_private = True
user_folder_access_rights = AccessRights(read=True, write=True, delete=True)

if workspace_id:
await check_user_workspace_access(
user_workspace_access_rights = await check_user_workspace_access(
app,
user_id=user_id,
workspace_id=workspace_id,
product_name=product_name,
permission="read",
)
workspace_is_private = False
user_folder_access_rights = user_workspace_access_rights.my_access_rights

if folder_id:
# Check user access to folder
Expand Down Expand Up @@ -178,6 +189,8 @@ async def list_folders(
created_at=folder.created,
modified_at=folder.modified,
owner=folder.created_by_gid,
workspace_id=folder.workspace_id,
my_access_rights=user_folder_access_rights,
)
for folder in folders
],
Expand All @@ -199,18 +212,19 @@ async def update_folder(
)

workspace_is_private = True
user_folder_access_rights = AccessRights(read=True, write=True, delete=True)
if folder_db.workspace_id:
await check_user_workspace_access(
user_workspace_access_rights = await check_user_workspace_access(
app,
user_id=user_id,
workspace_id=folder_db.workspace_id,
product_name=product_name,
permission="write",
)
workspace_is_private = False
user_folder_access_rights = user_workspace_access_rights.my_access_rights

# Check user has acces to the folder
# NOTE: MD: TODO check function!
await folders_db.get_for_user_or_workspace(
app,
folder_id=folder_id,
Expand All @@ -233,6 +247,8 @@ async def update_folder(
created_at=folder_db.created,
modified_at=folder_db.modified,
owner=folder_db.created_by_gid,
workspace_id=folder_db.workspace_id,
my_access_rights=user_folder_access_rights,
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from models_library.users import UserID
from models_library.utils.fastapi_encoders import jsonable_encoder
from models_library.utils.json_serialization import json_dumps
from models_library.workspaces import WorkspaceID
from pydantic import parse_obj_as
from servicelib.aiohttp.long_running_tasks.server import TaskProgress
from servicelib.mimetype_constants import MIMETYPE_APPLICATION_JSON
Expand All @@ -27,13 +26,15 @@
from ..application_settings import get_application_settings
from ..catalog import client as catalog_client
from ..director_v2 import api
from ..folders import _folders_db as folders_db
from ..storage.api import (
copy_data_folders_from_project,
get_project_total_size_simcore_s3,
)
from ..users.api import get_user_fullname
from ..workspaces.api import get_workspace
from ..workspaces.api import check_user_workspace_access
from ..workspaces.errors import WorkspaceAccessForbiddenError
from . import _folders_db as project_to_folders_db
from . import projects_api
from ._metadata_api import set_project_ancestors
from ._permalink_api import update_or_pop_permalink_in_project
Expand Down Expand Up @@ -233,7 +234,6 @@ async def create_project( # pylint: disable=too-many-arguments,too-many-branche
simcore_user_agent: str,
parent_project_uuid: ProjectID | None,
parent_node_id: NodeID | None,
workspace_id: WorkspaceID | None,
) -> None:
"""Implements TaskProtocol for 'create_projects' handler

Expand Down Expand Up @@ -264,8 +264,30 @@ async def create_project( # pylint: disable=too-many-arguments,too-many-branche
project_nodes = None
try:
task_progress.update(message="creating new study...")

workspace_id = None
folder_id = None
if predefined_project:
if workspace_id := predefined_project.get("workspaceId", None):
await check_user_workspace_access(
request.app,
user_id=user_id,
workspace_id=workspace_id,
product_name=product_name,
permission="write",
)
if folder_id := predefined_project.get("folderId", None):
# Check user has access to folder
await folders_db.get_for_user_or_workspace(
request.app,
folder_id=folder_id,
product_name=product_name,
user_id=user_id if workspace_id is None else None,
workspace_id=workspace_id,
)

if from_study:
# 1. prepare copy
# 1.1 prepare copy
(
new_project,
project_node_coro,
Expand All @@ -281,6 +303,19 @@ async def create_project( # pylint: disable=too-many-arguments,too-many-branche
if project_node_coro:
project_nodes = await project_node_coro

# 1.2 does project belong to some folder?
workspace_id = new_project["workspaceId"]
prj_to_folder_db = await project_to_folders_db.get_project_to_folder(
request.app,
project_id=from_study,
private_workspace_user_id_or_none=user_id
if workspace_id is None
else None,
)
if prj_to_folder_db:
# As user has access to the project, it has implicitly access to the folder
folder_id = prj_to_folder_db.folder_id

if predefined_project:
# 2. overrides with optional body and re-validate
new_project, project_nodes = await _compose_project_data(
Expand All @@ -290,21 +325,7 @@ async def create_project( # pylint: disable=too-many-arguments,too-many-branche
predefined_project=predefined_project,
)

# If user wants to create project in specific workspace
if workspace_id:
# Verify user access to the specified workspace; raise an error if access is denied
workspace = await get_workspace(
request.app,
user_id=user_id,
workspace_id=workspace_id,
product_name=product_name,
)
if workspace.my_access_rights.write is False:
raise WorkspaceAccessForbiddenError(
reason=f"User {user_id} does not have write permission on workspace {workspace_id}."
)

# 3. save new project in DB
# 3.1 save new project in DB
new_project = await db.insert_project(
project=jsonable_encoder(new_project),
user_id=user_id,
Expand All @@ -323,6 +344,17 @@ async def create_project( # pylint: disable=too-many-arguments,too-many-branche
)
task_progress.update()

# 3.2 move project to proper folder
if folder_id:
await project_to_folders_db.insert_project_to_folder(
request.app,
project_id=new_project["uuid"],
folder_id=folder_id,
private_workspace_user_id_or_none=(
user_id if workspace_id is None else None
),
)

# 4. deep copy source project's files
if copy_file_coro:
# NOTE: storage needs to have access to the new project prior to copying files
Expand Down
Loading
Loading