Skip to content

feat(api): create/list/retrieve/update workspaces via the token API#9400

Open
clwluvw wants to merge 1 commit into
makeplane:previewfrom
clwluvw:workspace-api
Open

feat(api): create/list/retrieve/update workspaces via the token API#9400
clwluvw wants to merge 1 commit into
makeplane:previewfrom
clwluvw:workspace-api

Conversation

@clwluvw

@clwluvw clwluvw commented Jul 11, 2026

Copy link
Copy Markdown

Description

Add a public (API-token) workspace surface so a workspace can be provisioned headlessly, mirroring the internal app-API create logic (plane/app/views/workspace/base.py::WorkSpaceViewSet).

  • POST /api/v1/workspaces/ -> create; token user becomes owner + admin (WorkspaceMember role 20); seeds default data via workspace_seed.delay. Slug is optional and auto-generated from the name (slugify, 48-char cap, numeric de-dup, reserved-slug aware) for headless provisioning; an explicit slug is validated exactly like the internal API.
  • GET /api/v1/workspaces/ -> list workspaces the user is a member of.
  • GET /api/v1/workspaces/{slug}/ -> retrieve (members only, else 404).
  • PATCH /api/v1/workspaces/{slug}/ -> partial update (admin only, matching
    the internal @allow_permission([ROLE.ADMIN]) gate).

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • Feature (non-breaking change which adds functionality)
  • Improvement (change that would cause existing functionality to not work as expected)
  • Code refactoring
  • Performance improvements
  • Documentation update

Summary by CodeRabbit

  • New Features
    • Added public workspace API endpoints for listing/creation and retrieve/update.
    • Automatically generates unique workspace slugs (with collision avoidance).
    • Exposes workspace membership context (member count and caller role).
  • Bug Fixes
    • Enforces stricter workspace name/slug validation, including rejection of URL-like names and restricted slugs.
    • Returns correct status codes for unauthorized access, non-member visibility, and slug uniqueness conflicts.
  • Tests
    • Added comprehensive contract tests covering validation, permissions, pagination behavior, and uniqueness error handling.

Copilot AI review requested due to automatic review settings July 11, 2026 22:54
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cc7d2291-e9f6-42d3-817c-6eecdf9ec97b

📥 Commits

Reviewing files that changed from the base of the PR and between 8d51118 and 05ac54b.

📒 Files selected for processing (7)
  • apps/api/plane/api/serializers/__init__.py
  • apps/api/plane/api/serializers/workspace.py
  • apps/api/plane/api/urls/__init__.py
  • apps/api/plane/api/urls/workspace.py
  • apps/api/plane/api/views/__init__.py
  • apps/api/plane/api/views/workspace.py
  • apps/api/plane/tests/contract/api/test_workspaces.py
✅ Files skipped from review due to trivial changes (1)
  • apps/api/plane/api/views/init.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • apps/api/plane/api/urls/init.py
  • apps/api/plane/api/urls/workspace.py
  • apps/api/plane/api/serializers/init.py
  • apps/api/plane/api/views/workspace.py
  • apps/api/plane/api/serializers/workspace.py

📝 Walkthrough

Walkthrough

Adds public token-based workspace list, create, retrieve, and update APIs with serializer validation, membership-scoped access, slug generation, permission handling, routing, and contract tests.

Changes

Workspace token API

Layer / File(s) Summary
Workspace serializer contract
apps/api/plane/api/serializers/workspace.py, apps/api/plane/api/serializers/__init__.py
Adds WorkspaceSerializer with derived fields, read-only fields, name and slug validation, and package re-exporting.
Workspace routes and endpoint flow
apps/api/plane/api/views/workspace.py, apps/api/plane/api/views/__init__.py, apps/api/plane/api/urls/workspace.py, apps/api/plane/api/urls/__init__.py
Registers workspace routes and implements membership-scoped listing, creation, retrieval, updates, slug generation, membership creation, permission checks, conflict handling, and workspace seeding.
Workspace API contract tests
apps/api/plane/tests/contract/api/test_workspaces.py
Tests authentication, validation, slug generation, membership filtering, retrieval, updates, permissions, and seeding dispatch.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TokenClient
  participant WorkspaceAPI
  participant WorkspaceSerializer
  participant WorkspaceDatabase
  participant WorkspaceSeeder
  TokenClient->>WorkspaceAPI: Request workspace list, creation, retrieval, or update
  WorkspaceAPI->>WorkspaceDatabase: Scope access to active membership
  WorkspaceAPI->>WorkspaceSerializer: Validate or serialize workspace data
  WorkspaceSerializer-->>WorkspaceAPI: Return validated or serialized data
  WorkspaceAPI->>WorkspaceDatabase: Persist workspace and creator membership
  WorkspaceAPI->>WorkspaceSeeder: Dispatch workspace seeding
  WorkspaceAPI-->>TokenClient: Return workspace response
Loading

Possibly related PRs

  • makeplane/plane#9278: Adds similar workspace name validation for URL-containing and symbol-only names.

Suggested reviewers: dheeru0198, pablohashescobar, sriramveeraghanta, Prashant-Surya

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.95% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title concisely summarizes the main change: public token API endpoints for workspace CRUD operations.
Description check ✅ Passed The description covers the change and type of change, but it does not fill the screenshots, test scenarios, or references sections.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new public (API-token) workspace surface to allow headless provisioning and workspace management, mirroring key behaviors of the internal app workspace endpoints.

Changes:

  • Introduces token-API endpoints to create/list/retrieve/patch workspaces, including auto-generated unique slugs and background seeding.
  • Adds a full workspace serializer for the token API with name/slug validation aligned to the internal app serializer.
  • Wires new workspace URL patterns with precedence handling and adds contract tests for the new endpoints and slug generator.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
apps/api/plane/tests/contract/api/test_workspaces.py Adds contract tests for workspace list/create/detail/patch and slug generation.
apps/api/plane/api/views/workspace.py Implements token-API workspace endpoints plus unique slug generator and annotated queryset.
apps/api/plane/api/views/init.py Exports the new workspace API views.
apps/api/plane/api/urls/workspace.py Adds URL patterns for /workspaces/ and /workspaces/{slug}/.
apps/api/plane/api/urls/init.py Registers workspace URLs ahead of router-mounted conflicting paths.
apps/api/plane/api/serializers/workspace.py Adds WorkspaceSerializer (token API) mirroring internal validation rules.
apps/api/plane/api/serializers/init.py Exposes WorkspaceSerializer from the serializers package.

Comment thread apps/api/plane/api/views/workspace.py Outdated
Comment thread apps/api/plane/api/views/workspace.py Outdated
Comment thread apps/api/plane/tests/contract/api/test_workspaces.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
apps/api/plane/api/serializers/workspace.py (1)

43-45: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

logo_url is redundantly listed in read_only_fields.

logo_url is already declared as serializers.CharField(read_only=True) on line 44, so its entry in Meta.read_only_fields on line 79 is a no-op. Remove it from read_only_fields to avoid confusion.

♻️ Remove redundant entry
         read_only_fields = [
             "id",
             "created_by",
             "updated_by",
             "created_at",
             "updated_at",
             "owner",
-            "logo_url",
         ]

Also applies to: 79-79

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/plane/api/serializers/workspace.py` around lines 43 - 45, Remove
logo_url from the serializer’s Meta.read_only_fields declaration, keeping its
existing serializers.CharField(read_only=True) field declaration unchanged.
apps/api/plane/tests/contract/api/test_workspaces.py (2)

66-170: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test for the DISABLE_WORKSPACE_CREATION gate.

The POST handler checks DISABLE_WORKSPACE_CREATION and returns 403, but no test covers this path. Adding a test ensures the gate is not accidentally bypassed in future refactors.

✨ Suggested test: workspace creation disabled
`@pytest.mark.django_db`
`@mock.patch`("plane.api.views.workspace.get_configuration_value")
def test_create_workspace_disabled(self, mock_config, api_key_client):
    mock_config.return_value = ["1"]
    response = api_key_client.post(LIST_CREATE_URL, {"name": "Acme", "slug": "acme"}, format="json")
    assert response.status_code == status.HTTP_403_FORBIDDEN
    assert Workspace.objects.count() == 0
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/plane/tests/contract/api/test_workspaces.py` around lines 66 - 170,
Add a Django database test to TestWorkspaceListCreateAPIEndpoint covering the
DISABLE_WORKSPACE_CREATION gate. Patch
plane.api.views.workspace.get_configuration_value to return ["1"], submit a
valid POST through api_key_client, and assert HTTP 403 with no Workspace
created.

173-226: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test for a non-owner admin updating a workspace.

The test suite covers a plain member (role 15) being denied PATCH, but there is no test for a non-owner admin (role 20). This gap directly relates to the WorkspaceOwnerPermission vs @allow_permission([ROLE.ADMIN]) discrepancy flagged in views/workspace.py. If the permission only allows the owner, a non-owner admin would be incorrectly blocked. Add a test to clarify the expected behavior.

✨ Suggested test: non-owner admin update
`@pytest.mark.django_db`
def test_non_owner_admin_can_update(self, api_key_client, admin_workspace):
    """A non-owner admin (role 20) should be able to update the workspace,
    mirroring the internal `@allow_permission`([ROLE.ADMIN]) behavior."""
    response = api_key_client.patch(
        detail_url(admin_workspace.slug), {"name": "Admin Rename"}, format="json"
    )
    assert response.status_code == status.HTTP_200_OK, f"Got {response.status_code}: {response.data!r}"
    admin_workspace.refresh_from_db()
    assert admin_workspace.name == "Admin Rename"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/plane/tests/contract/api/test_workspaces.py` around lines 173 - 226,
Add a django_db test in TestWorkspaceDetailAPIEndpoint alongside
test_member_can_retrieve_but_not_update that uses the admin_workspace fixture to
PATCH the workspace as a non-owner admin. Assert a 200 OK response and verify
after refresh_from_db that the workspace name changed to the submitted value,
documenting that role 20 admins may update.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/plane/api/views/workspace.py`:
- Around line 206-208: Update the total_members calculation in the POST handler
to match the workspace_queryset annotation by filtering WorkspaceMember records
for the workspace with member__is_bot=False and is_active=True before counting.
Leave the response_data construction unchanged.
- Around line 193-221: Wrap the workspace creation and owner membership
insertion in WorkspaceViewSet.create using transaction.atomic(), including
serializer.save and WorkspaceMember.objects.create in the same atomic block.
Keep the response and background seeding after the transaction succeeds, and
ensure IntegrityError handling only maps the intended workspace slug conflict
rather than masking membership failures.

---

Nitpick comments:
In `@apps/api/plane/api/serializers/workspace.py`:
- Around line 43-45: Remove logo_url from the serializer’s Meta.read_only_fields
declaration, keeping its existing serializers.CharField(read_only=True) field
declaration unchanged.

In `@apps/api/plane/tests/contract/api/test_workspaces.py`:
- Around line 66-170: Add a Django database test to
TestWorkspaceListCreateAPIEndpoint covering the DISABLE_WORKSPACE_CREATION gate.
Patch plane.api.views.workspace.get_configuration_value to return ["1"], submit
a valid POST through api_key_client, and assert HTTP 403 with no Workspace
created.
- Around line 173-226: Add a django_db test in TestWorkspaceDetailAPIEndpoint
alongside test_member_can_retrieve_but_not_update that uses the admin_workspace
fixture to PATCH the workspace as a non-owner admin. Assert a 200 OK response
and verify after refresh_from_db that the workspace name changed to the
submitted value, documenting that role 20 admins may update.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 694cc143-ca8d-43a2-979a-640bb6584cb5

📥 Commits

Reviewing files that changed from the base of the PR and between dc9d80b and 8d51118.

📒 Files selected for processing (7)
  • apps/api/plane/api/serializers/__init__.py
  • apps/api/plane/api/serializers/workspace.py
  • apps/api/plane/api/urls/__init__.py
  • apps/api/plane/api/urls/workspace.py
  • apps/api/plane/api/views/__init__.py
  • apps/api/plane/api/views/workspace.py
  • apps/api/plane/tests/contract/api/test_workspaces.py

Comment thread apps/api/plane/api/views/workspace.py Outdated
Comment thread apps/api/plane/api/views/workspace.py Outdated
Add a public (API-token) workspace surface so a workspace can be
provisioned headlessly, mirroring the internal app-API create logic
(plane/app/views/workspace/base.py::WorkSpaceViewSet).

- POST /api/v1/workspaces/  -> create; token user becomes owner + admin
  (WorkspaceMember role 20); seeds default data via workspace_seed.delay.
  Slug is optional and auto-generated from the name (slugify, 48-char
  cap, numeric de-dup, reserved-slug aware) for headless provisioning;
  an explicit slug is validated exactly like the internal API.
- GET  /api/v1/workspaces/  -> list workspaces the user is a member of.
- GET  /api/v1/workspaces/{slug}/   -> retrieve (members only, else 404).
- PATCH /api/v1/workspaces/{slug}/  -> partial update (admin only, matching
  the internal @allow_permission([ROLE.ADMIN]) gate).

Validation mirrors WorkSpaceSerializer: name <=80 + has_alphanumeric +
no embedded URL; slug <=48 + ^[a-zA-Z0-9_-]+$ + RESTRICTED_WORKSPACE_SLUGS;
owner is read-only; DISABLE_WORKSPACE_CREATION honoured.

Routes are registered ahead of the invite/sticky DefaultRouters so the
workspaces/{slug}/ detail route wins over their API-root views. Additive
only; no model changes or migrations. Adds contract + unit tests.

Signed-off-by: Seena Fallah <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants