Skip to content

Commit

Permalink
✨ Trash workspaces (#6690)
Browse files Browse the repository at this point in the history
  • Loading branch information
pcrespov authored Nov 23, 2024
1 parent 1eb6dd0 commit 96d7671
Show file tree
Hide file tree
Showing 74 changed files with 1,349 additions and 669 deletions.
8 changes: 4 additions & 4 deletions api/specs/web-server/_folders.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
from _common import as_query
from fastapi import APIRouter, Depends, status
from models_library.api_schemas_webserver.folders_v2 import (
CreateFolderBodyParams,
FolderCreateBodyParams,
FolderGet,
PutFolderBodyParams,
FolderReplaceBodyParams,
)
from models_library.generics import Envelope
from simcore_service_webserver._meta import API_VTAG
Expand All @@ -38,7 +38,7 @@
status_code=status.HTTP_201_CREATED,
)
async def create_folder(
_body: CreateFolderBodyParams,
_body: FolderCreateBodyParams,
):
...

Expand Down Expand Up @@ -79,7 +79,7 @@ async def get_folder(
)
async def replace_folder(
_path: Annotated[FoldersPathParams, Depends()],
_body: PutFolderBodyParams,
_body: FolderReplaceBodyParams,
):
...

Expand Down
45 changes: 40 additions & 5 deletions api/specs/web-server/_trash.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@
from typing import Annotated

from fastapi import APIRouter, Depends, status
from models_library.trash import RemoveQueryParams
from simcore_service_webserver._meta import API_VTAG
from simcore_service_webserver.folders._models import (
FoldersPathParams,
RemoveQueryParams,
FolderTrashQueryParams,
)
from simcore_service_webserver.projects._trash_handlers import ProjectPathParams
from simcore_service_webserver.projects._trash_handlers import (
RemoveQueryParams as RemoveQueryParams_duplicated,
from simcore_service_webserver.workspaces._models import (
WorkspacesPathParams,
WorkspaceTrashQueryParams,
)

router = APIRouter(
Expand Down Expand Up @@ -75,14 +77,14 @@ def untrash_project(
responses={
status.HTTP_404_NOT_FOUND: {"description": "Not such a folder"},
status.HTTP_409_CONFLICT: {
"description": "One or more projects is in use and cannot be trashed"
"description": "One or more projects in the folder are in use and cannot be trashed"
},
status.HTTP_503_SERVICE_UNAVAILABLE: {"description": "Trash service error"},
},
)
def trash_folder(
_path: Annotated[FoldersPathParams, Depends()],
_query: Annotated[RemoveQueryParams_duplicated, Depends()],
_query: Annotated[FolderTrashQueryParams, Depends()],
):
...

Expand All @@ -96,3 +98,36 @@ def untrash_folder(
_path: Annotated[FoldersPathParams, Depends()],
):
...


_extra_tags = ["workspaces"]


@router.post(
"/workspaces/{workspace_id}:trash",
tags=_extra_tags,
status_code=status.HTTP_204_NO_CONTENT,
responses={
status.HTTP_404_NOT_FOUND: {"description": "Not such a workspace"},
status.HTTP_409_CONFLICT: {
"description": "One or more projects in the workspace are in use and cannot be trashed"
},
status.HTTP_503_SERVICE_UNAVAILABLE: {"description": "Trash service error"},
},
)
def trash_workspace(
_path: Annotated[WorkspacesPathParams, Depends()],
_query: Annotated[WorkspaceTrashQueryParams, Depends()],
):
...


@router.post(
"/workspaces/{workspace_id}:untrash",
tags=_extra_tags,
status_code=status.HTTP_204_NO_CONTENT,
)
def untrash_workspace(
_path: Annotated[WorkspacesPathParams, Depends()],
):
...
52 changes: 32 additions & 20 deletions api/specs/web-server/_workspaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,24 @@
# pylint: disable=unused-variable
# pylint: disable=too-many-arguments


from enum import Enum
from typing import Annotated

from _common import as_query
from fastapi import APIRouter, Depends, status
from models_library.api_schemas_webserver.workspaces import (
CreateWorkspaceBodyParams,
PutWorkspaceBodyParams,
WorkspaceCreateBodyParams,
WorkspaceGet,
WorkspaceReplaceBodyParams,
)
from models_library.generics import Envelope
from models_library.workspaces import WorkspaceID
from simcore_service_webserver._meta import API_VTAG
from simcore_service_webserver.workspaces._groups_api import WorkspaceGroupGet
from simcore_service_webserver.workspaces._groups_handlers import (
_WorkspacesGroupsBodyParams,
_WorkspacesGroupsPathParams,
from simcore_service_webserver.workspaces._models import (
WorkspacesGroupsBodyParams,
WorkspacesGroupsPathParams,
WorkspacesListQueryParams,
WorkspacesPathParams,
)

router = APIRouter(
Expand All @@ -32,47 +33,56 @@
],
)

### Workspaces


@router.post(
"/workspaces",
response_model=Envelope[WorkspaceGet],
status_code=status.HTTP_201_CREATED,
)
async def create_workspace(_body: CreateWorkspaceBodyParams):
async def create_workspace(
_body: WorkspaceCreateBodyParams,
):
...


@router.get(
"/workspaces",
response_model=Envelope[list[WorkspaceGet]],
)
async def list_workspaces():
async def list_workspaces(
_query: Annotated[as_query(WorkspacesListQueryParams), Depends()],
):
...


@router.get(
"/workspaces/{workspace_id}",
response_model=Envelope[WorkspaceGet],
)
async def get_workspace(workspace_id: WorkspaceID):
async def get_workspace(
_path: Annotated[WorkspacesPathParams, Depends()],
):
...


@router.put(
"/workspaces/{workspace_id}",
response_model=Envelope[WorkspaceGet],
)
async def replace_workspace(workspace_id: WorkspaceID, _body: PutWorkspaceBodyParams):
async def replace_workspace(
_path: Annotated[WorkspacesPathParams, Depends()],
_body: WorkspaceReplaceBodyParams,
):
...


@router.delete(
"/workspaces/{workspace_id}",
status_code=status.HTTP_204_NO_CONTENT,
)
async def delete_workspace(workspace_id: WorkspaceID):
async def delete_workspace(
_path: Annotated[WorkspacesPathParams, Depends()],
):
...


Expand All @@ -87,8 +97,8 @@ async def delete_workspace(workspace_id: WorkspaceID):
tags=_extra_tags,
)
async def create_workspace_group(
_path_parms: Annotated[_WorkspacesGroupsPathParams, Depends()],
_body: _WorkspacesGroupsBodyParams,
_path: Annotated[WorkspacesGroupsPathParams, Depends()],
_body: WorkspacesGroupsBodyParams,
):
...

Expand All @@ -98,7 +108,9 @@ async def create_workspace_group(
response_model=Envelope[list[WorkspaceGroupGet]],
tags=_extra_tags,
)
async def list_workspace_groups(workspace_id: WorkspaceID):
async def list_workspace_groups(
_path: Annotated[WorkspacesPathParams, Depends()],
):
...


Expand All @@ -108,8 +120,8 @@ async def list_workspace_groups(workspace_id: WorkspaceID):
tags=_extra_tags,
)
async def replace_workspace_group(
_path_parms: Annotated[_WorkspacesGroupsPathParams, Depends()],
_body: _WorkspacesGroupsBodyParams,
_path: Annotated[WorkspacesGroupsPathParams, Depends()],
_body: WorkspacesGroupsBodyParams,
):
...

Expand All @@ -120,6 +132,6 @@ async def replace_workspace_group(
tags=_extra_tags,
)
async def delete_workspace_group(
_path_parms: Annotated[_WorkspacesGroupsPathParams, Depends()]
_path: Annotated[WorkspacesGroupsPathParams, Depends()],
):
...
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class FolderGetPage(NamedTuple):
total: PositiveInt


class CreateFolderBodyParams(InputSchema):
class FolderCreateBodyParams(InputSchema):
name: IDStr
parent_folder_id: FolderID | None = None
workspace_id: WorkspaceID | None = None
Expand All @@ -44,7 +44,7 @@ class CreateFolderBodyParams(InputSchema):
)(null_or_none_str_to_none_validator)


class PutFolderBodyParams(InputSchema):
class FolderReplaceBodyParams(InputSchema):
name: IDStr
parent_folder_id: FolderID | None = None
model_config = ConfigDict(extra="forbid")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from pydantic import ConfigDict, PositiveInt

from ..access_rights import AccessRights
from ..users import UserID
from ._base import InputSchema, OutputSchema


Expand All @@ -17,6 +18,8 @@ class WorkspaceGet(OutputSchema):
thumbnail: str | None
created_at: datetime
modified_at: datetime
trashed_at: datetime | None
trashed_by: UserID | None
my_access_rights: AccessRights
access_rights: dict[GroupID, AccessRights]

Expand All @@ -26,15 +29,15 @@ class WorkspaceGetPage(NamedTuple):
total: PositiveInt


class CreateWorkspaceBodyParams(InputSchema):
class WorkspaceCreateBodyParams(InputSchema):
name: str
description: str | None = None
thumbnail: str | None = None

model_config = ConfigDict(extra="forbid")


class PutWorkspaceBodyParams(InputSchema):
class WorkspaceReplaceBodyParams(InputSchema):
name: IDStr
description: str | None = None
thumbnail: str | None = None
Expand Down
7 changes: 7 additions & 0 deletions packages/models-library/src/models_library/trash.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from pydantic import BaseModel, Field


class RemoveQueryParams(BaseModel):
force: bool = Field(
default=False, description="Force removal (even if resource is active)"
)
16 changes: 13 additions & 3 deletions packages/models-library/src/models_library/workspaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
)

from .access_rights import AccessRights
from .users import GroupID
from .users import GroupID, UserID
from .utils.enums import StrAutoEnum

WorkspaceID: TypeAlias = PositiveInt
Expand All @@ -33,10 +33,10 @@ class WorkspaceQuery(BaseModel):
def validate_workspace_id(cls, value, info: ValidationInfo):
scope = info.data.get("workspace_scope")
if scope == WorkspaceScope.SHARED and value is None:
msg = "workspace_id must be provided when workspace_scope is SHARED."
msg = f"workspace_id must be provided when workspace_scope is SHARED. Got {scope=}, {value=}"
raise ValueError(msg)
if scope != WorkspaceScope.SHARED and value is not None:
msg = "workspace_id should be None when workspace_scope is not SHARED."
msg = f"workspace_id should be None when workspace_scope is not SHARED. Got {scope=}, {value=}"
raise ValueError(msg)
return value

Expand All @@ -63,6 +63,8 @@ class WorkspaceDB(BaseModel):
...,
description="Timestamp of last modification",
)
trashed: datetime | None
trashed_by: UserID | None

model_config = ConfigDict(from_attributes=True)

Expand All @@ -72,3 +74,11 @@ class UserWorkspaceAccessRightsDB(WorkspaceDB):
access_rights: dict[GroupID, AccessRights]

model_config = ConfigDict(from_attributes=True)


class WorkspaceUpdateDB(BaseModel):
name: str | None = None
description: str | None = None
thumbnail: str | None = None
trashed: datetime | None = None
trashed_by: UserID | None = None
Loading

0 comments on commit 96d7671

Please sign in to comment.