Skip to content

Commit

Permalink
Add workspace settings endpoints (#199)
Browse files Browse the repository at this point in the history
  • Loading branch information
meln1k authored Dec 5, 2023
1 parent 042e5cc commit fa81c3d
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 10 deletions.
22 changes: 21 additions & 1 deletion fixbackend/workspaces/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from fixbackend.auth.models import orm as auth_orm
from fixbackend.dependencies import FixDependency, ServiceNames
from fixbackend.graph_db.service import GraphDatabaseAccessManager
from fixbackend.ids import WorkspaceId, UserId
from fixbackend.ids import ExternalId, WorkspaceId, UserId
from fixbackend.types import AsyncSessionMaker
from fixbackend.workspaces.models import Workspace, WorkspaceInvite, orm
from fixbackend.domain_events.publisher import DomainEventPublisher
Expand All @@ -49,6 +49,11 @@ async def list_workspaces(self, user_id: UserId) -> Sequence[Workspace]:
"""List all workspaces where the user is a member."""
raise NotImplementedError

@abstractmethod
async def update_workspace(self, workspace_id: WorkspaceId, name: str, generate_external_id: bool) -> Workspace:
"""Update a workspace."""
raise NotImplementedError

@abstractmethod
async def add_to_workspace(self, workspace_id: WorkspaceId, user_id: UserId) -> None:
"""Add a user to a workspace."""
Expand Down Expand Up @@ -125,6 +130,21 @@ async def get_workspace(self, workspace_id: WorkspaceId) -> Optional[Workspace]:
org = results.unique().scalar_one_or_none()
return org.to_model() if org else None

async def update_workspace(self, workspace_id: WorkspaceId, name: str, generate_external_id: bool) -> Workspace:
"""Update a workspace."""
async with self.session_maker() as session:
statement = select(orm.Organization).where(orm.Organization.id == workspace_id)
results = await session.execute(statement)
org = results.unique().scalar_one_or_none()
if org is None:
raise ValueError(f"Organization {workspace_id} does not exist.")
org.name = name
if generate_external_id:
org.external_id = ExternalId(uuid.uuid4())
await session.commit()
await session.refresh(org)
return org.to_model()

async def list_workspaces(self, user_id: UserId) -> Sequence[Workspace]:
async with self.session_maker() as session:
statement = (
Expand Down
34 changes: 31 additions & 3 deletions fixbackend/workspaces/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,14 @@
from fixbackend.ids import WorkspaceId
from fixbackend.workspaces.repository import WorkspaceRepositoryDependency
from fixbackend.workspaces.dependencies import UserWorkspaceDependency
from fixbackend.workspaces.schemas import ExternalId, WorkspaceCreate, WorkspaceInviteRead, WorkspaceRead
from fixbackend.workspaces.schemas import (
ExternalIdRead,
WorkspaceCreate,
WorkspaceInviteRead,
WorkspaceRead,
WorkspaceSettingsRead,
WorkspaceSettingsUpdate,
)


def workspaces_router() -> APIRouter:
Expand Down Expand Up @@ -56,6 +63,27 @@ async def get_workspace(

return WorkspaceRead.from_model(org)

@router.get("/{workspace_id}/settings")
async def get_workspace_settings(
workspace: UserWorkspaceDependency,
) -> WorkspaceSettingsRead:
"""Get a workspace."""
return WorkspaceSettingsRead.from_model(workspace)

@router.patch("/{workspace_id}/settings")
async def update_workspace_settings(
workspace: UserWorkspaceDependency,
settings: WorkspaceSettingsUpdate,
workspace_repository: WorkspaceRepositoryDependency,
) -> WorkspaceSettingsRead:
"""Update a workspace."""
org = await workspace_repository.update_workspace(
workspace_id=workspace.id,
name=settings.name,
generate_external_id=settings.generate_new_external_id,
)
return WorkspaceSettingsRead.from_model(org)

@router.post("/")
async def create_workspace(
organization: WorkspaceCreate,
Expand Down Expand Up @@ -163,8 +191,8 @@ async def get_cf_template(
@router.get("/{workspace_id}/external_id")
async def get_external_id(
workspace: UserWorkspaceDependency,
) -> ExternalId:
) -> ExternalIdRead:
"""Get a workspaces external id."""
return ExternalId(external_id=workspace.external_id)
return ExternalIdRead(external_id=workspace.external_id)

return router
41 changes: 37 additions & 4 deletions fixbackend/workspaces/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@

from datetime import datetime
from typing import List
from uuid import UUID
from fixbackend.ids import WorkspaceId, UserId
from fixbackend.ids import WorkspaceId, UserId, ExternalId

from pydantic import BaseModel, Field

Expand Down Expand Up @@ -54,6 +53,40 @@ def from_model(cls, model: Workspace) -> "WorkspaceRead":
)


class WorkspaceSettingsRead(BaseModel):
id: WorkspaceId = Field(description="The workspace's unique identifier")
slug: str = Field(description="The workspace's unique slug, used in URLs")
name: str = Field(description="The workspace's name, a human-readable string")
external_id: ExternalId = Field(description="The workspace's external identifier")

model_config = {
"json_schema_extra": {
"examples": [
{
"id": "00000000-0000-0000-0000-000000000000",
"slug": "my-org",
"name": "My Organization",
"external_id": "00000000-0000-0000-0000-000000000000",
}
]
}
}

@classmethod
def from_model(cls, model: Workspace) -> "WorkspaceSettingsRead":
return WorkspaceSettingsRead(
id=model.id,
slug=model.slug,
name=model.name,
external_id=model.external_id,
)


class WorkspaceSettingsUpdate(BaseModel):
name: str = Field(description="The workspace's name, a human-readable string")
generate_new_external_id: bool = Field(description="Whether to generate a new external identifier")


class WorkspaceCreate(BaseModel):
name: str = Field(description="Workspace name, a human-readable string")
slug: str = Field(description="Workspace unique slug, used in URLs", pattern="^[a-z0-9-]+$")
Expand Down Expand Up @@ -88,8 +121,8 @@ class WorkspaceInviteRead(BaseModel):
}


class ExternalId(BaseModel):
external_id: UUID = Field(description="The organization's external identifier")
class ExternalIdRead(BaseModel):
external_id: ExternalId = Field(description="The organization's external identifier")

model_config = {
"json_schema_extra": {
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ async def user(session: AsyncSession) -> User:


@pytest.mark.asyncio
async def test_create_organization(workspace_repository: WorkspaceRepository, user: User) -> None:
async def test_create_workspace(workspace_repository: WorkspaceRepository, user: User) -> None:
organization = await workspace_repository.create_workspace(
name="Test Organization", slug="test-organization", owner=user
)
Expand All @@ -55,7 +55,7 @@ async def test_create_organization(workspace_repository: WorkspaceRepository, us


@pytest.mark.asyncio
async def test_get_organization(workspace_repository: WorkspaceRepository, user: User) -> None:
async def test_get_workspace(workspace_repository: WorkspaceRepository, user: User) -> None:
# we can get an existing organization by id
organization = await workspace_repository.create_workspace(
name="Test Organization", slug="test-organization", owner=user
Expand All @@ -69,6 +69,20 @@ async def test_get_organization(workspace_repository: WorkspaceRepository, user:
assert retrieved_organization is None


@pytest.mark.asyncio
async def test_update_workspace(workspace_repository: WorkspaceRepository, user: User) -> None:
# we can get an existing organization by id
workspace = await workspace_repository.create_workspace(
name="Test Organization", slug="test-organization", owner=user
)

await workspace_repository.update_workspace(workspace.id, "foobar", True)
new_workspace = await workspace_repository.get_workspace(workspace.id)
assert new_workspace is not None
assert new_workspace.name == "foobar"
assert new_workspace.external_id != workspace.external_id


@pytest.mark.asyncio
async def test_list_organizations(workspace_repository: WorkspaceRepository, user: User) -> None:
workspace1 = await workspace_repository.create_workspace(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import uuid
from typing import AsyncIterator, Sequence
from uuid import UUID
from attrs import evolve

import pytest
from httpx import AsyncClient
Expand Down Expand Up @@ -64,6 +65,13 @@ async def get_workspace(self, workspace_id: UUID) -> Workspace | None:
async def list_workspaces(self, owner_id: UUID) -> Sequence[Workspace]:
return [workspace]

async def update_workspace(self, workspace_id: WorkspaceId, name: str, generate_external_id: bool) -> Workspace:
if generate_external_id:
new_external_id = ExternalId(uuid.uuid4())
else:
new_external_id = workspace.external_id
return evolve(workspace, name=name, external_id=new_external_id)


@pytest.fixture
async def client(session: AsyncSession, default_config: Config) -> AsyncIterator[AsyncClient]: # noqa: F811
Expand Down Expand Up @@ -105,3 +113,22 @@ async def test_external_id(client: AsyncClient) -> None:
async def test_cloudformation_template_url(client: AsyncClient, default_config: Config) -> None:
response = await client.get(f"/api/workspaces/{org_id}/cf_template")
assert response.json() == str(default_config.cf_template_url)


@pytest.mark.asyncio
async def test_get_workspace_settings(client: AsyncClient, default_config: Config) -> None:
response = await client.get(f"/api/workspaces/{org_id}/settings")
assert response.json().get("id") == str(org_id)
assert response.json().get("slug") == workspace.slug
assert response.json().get("name") == workspace.name
assert response.json().get("external_id") == str(external_id)


@pytest.mark.asyncio
async def test_update_workspace_settings(client: AsyncClient, default_config: Config) -> None:
payload = {"name": "new name", "generate_new_external_id": True}
response = await client.patch(f"/api/workspaces/{org_id}/settings", json=payload)
assert response.json().get("id") == str(org_id)
assert response.json().get("slug") == workspace.slug
assert response.json().get("name") == "new name"
assert response.json().get("external_id") != str(external_id)

0 comments on commit fa81c3d

Please sign in to comment.