Skip to content

feat(api): add lite list endpoints for projects, members, cycles, and modules#9410

Open
akhil-vamshi-konam wants to merge 1 commit into
previewfrom
feat-lite-endpoints
Open

feat(api): add lite list endpoints for projects, members, cycles, and modules#9410
akhil-vamshi-konam wants to merge 1 commit into
previewfrom
feat-lite-endpoints

Conversation

@akhil-vamshi-konam

@akhil-vamshi-konam akhil-vamshi-konam commented Jul 13, 2026

Copy link
Copy Markdown

Description

Adds lightweight, cursor-paginated list endpoints for projects, workspace members, project members, cycles, and modules to support picker/reference use cases with reduced payload sizes.

Changes

  • Added new lite endpoints:

    • GET /workspaces/{slug}/projects-lite/
    • GET /workspaces/{slug}/members-lite/
    • GET /workspaces/{slug}/projects/{project_id}/project-members-lite/
    • GET /workspaces/{slug}/projects/{project_id}/cycles-lite/
    • GET /workspaces/{slug}/projects/{project_id}/modules-lite/
  • Introduced lightweight serializers for projects and members.

  • Added optional include_archived query parameter for the projects-lite endpoint.

  • Added OpenAPI documentation for all new endpoints.

  • Added contract tests covering response shape, pagination, archived filtering, and error scenarios.

  • Updated issue listing to return a clear 400 response when unsupported pql or filters query parameters are provided.

Summary by CodeRabbit

  • New Features

    • Added lightweight, paginated API listings for projects, cycles, modules, workspace members, and project members.
    • Project listings now include archive status and support optionally showing archived projects.
    • Archived cycles and modules are excluded from lightweight listings.
    • Added clear validation errors for unsupported work-item filters.
  • Bug Fixes

    • Improved handling of invalid workspace and project requests.
  • Tests

    • Added coverage for pagination, permissions, archived-item filtering, response fields, and invalid requests.

@CLAassistant

CLAassistant commented Jul 13, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Added lightweight paginated API endpoints for projects, cycles, modules, workspace members, and project members. Added lite serializers, routing, archived-record filtering, contract tests, and validation rejecting unsupported issue filters.

Changes

Lite API surface

Layer / File(s) Summary
Response contracts and exports
apps/api/plane/api/serializers/*, apps/api/plane/api/views/__init__.py
Added flattened lite member serializers, exported new serializers and endpoints, and exposed archived_at in ProjectLiteSerializer.
Projects-lite listing
apps/api/plane/api/views/project.py, apps/api/plane/api/urls/project.py, apps/api/plane/tests/contract/api/test_projects_lite.py
Added a workspace-scoped paginated projects-lite endpoint with archive filtering, ordering, access filtering, and contract tests.
Cycles and modules-lite listings
apps/api/plane/api/views/{cycle,module}.py, apps/api/plane/api/urls/{cycle,module}.py, apps/api/plane/tests/contract/api/test_{cycles,modules}_lite.py
Added project-scoped paginated endpoints that exclude archived cycles and modules, with route wiring and contract tests.
Member-lite listings
apps/api/plane/api/views/member.py, apps/api/plane/api/urls/member.py, apps/api/plane/tests/contract/api/test_members_lite.py
Added workspace and project member-lite endpoints with permissions, related-object loading, pagination, and response-shape tests.
Issue filter validation
apps/api/plane/api/views/issue.py
Rejects issue list requests containing pql or filters with HTTP 400.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant API Route
  participant Lite Endpoint
  participant Database
  participant Lite Serializer
  Client->>API Route: GET lite collection
  API Route->>Lite Endpoint: Dispatch request
  Lite Endpoint->>Database: Query filtered records
  Database-->>Lite Endpoint: Paginated records
  Lite Endpoint->>Lite Serializer: Serialize results
  Lite Serializer-->>Client: Paginated lite response
Loading

Possibly related PRs

  • makeplane/plane#9349: Modifies the same issue list endpoint with a related request-time query parameter guard.

Suggested reviewers: dheeru0198, pablohashescobar

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.43% 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 clearly summarizes the main change: adding lite list endpoints for projects, members, cycles, and modules.
Description check ✅ Passed The description is detailed and matches the changes, but it omits the template sections for type of change, screenshots, tests, and references.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-lite-endpoints

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.

@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: 6

🧹 Nitpick comments (5)
apps/api/plane/tests/contract/api/test_members_lite.py (1)

86-102: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding error-scenario and role-assertion tests for TestProjectMembersLite.

TestWorkspaceMembersLite includes test_unknown_workspace_is_rejected and asserts item["role"] == 20 in test_lite_member_shape. TestProjectMembersLite has neither — no test for unknown workspace/project rejection, and test_lite_member_shape doesn't verify the role value. Adding these would match the workspace test coverage.

🤖 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_members_lite.py` around lines 86 -
102, Extend TestProjectMembersLite with an unknown workspace/project rejection
test matching TestWorkspaceMembersLite, using the existing URL helper and
expected error status. Update test_lite_member_shape to assert the returned
member’s role equals 20, while preserving the existing field-presence checks.
apps/api/plane/tests/contract/api/test_modules_lite.py (1)

52-61: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider asserting the lite field shape.

Same gap as test_cycles_lite.py: the test verifies pagination and archived exclusion but does not assert the lite field set. Adding field-shape assertions (like test_projects_lite.py does) would verify the "lite" contract.

🤖 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_modules_lite.py` around lines 52 - 61,
Update TestModulesLite.test_paginated_and_excludes_archived to assert that each
returned module contains exactly the expected lite response fields, following
the field-shape assertion pattern used by the related lite contract tests. Keep
the existing pagination, status, and archived-module assertions unchanged.
apps/api/plane/tests/contract/api/test_cycles_lite.py (2)

63-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider asserting the lite field shape.

The test verifies pagination and archived exclusion but does not assert which fields are present or absent in the response items. test_projects_lite.py includes test_returns_only_lite_fields that checks the lite field set and confirms heavy fields like total_members are absent. Adding a similar assertion here would verify the "lite" contract.

🤖 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_cycles_lite.py` around lines 63 - 72,
Update TestCyclesLite.test_paginated_and_excludes_archived to assert each
returned cycle item matches the lite response field set, including that heavy
fields such as total_members are absent. Reuse the established field-shape
assertion pattern from test_projects_lite.py while preserving the existing
pagination and archived-cycle checks.

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

Contract tests for cycles-lite and modules-lite don't verify the lite field shape. Both tests assert pagination and archived exclusion but omit field-shape checks — the "lite" contract (which fields are present/absent) is untested. test_projects_lite.py includes test_returns_only_lite_fields that checks the lite field set and confirms heavy fields are absent.

  • apps/api/plane/tests/contract/api/test_cycles_lite.py#L63-72: Add assertions verifying lite fields present (e.g., id, name, start_date, end_date) and computed/heavy fields absent.
  • apps/api/plane/tests/contract/api/test_modules_lite.py#L52-61: Add similar field-shape assertions for the modules-lite response items.
🤖 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_cycles_lite.py` at line 1, Update the
cycles-lite and modules-lite contract tests, specifically their response-item
assertions near the existing pagination and archived-exclusion checks, to verify
the expected lite fields are present and computed/heavy fields are absent.
Mirror the field-shape validation used by test_returns_only_lite_fields in
test_projects_lite.py, covering representative fields such as id, name,
start_date, and end_date where applicable.
apps/api/plane/api/views/cycle.py (1)

360-399: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

CycleLiteSerializer uses fields = "__all__", undermining the "lite" purpose.

The serializer docstring says "minimal data transfer" but fields = "__all__" serializes every Cycle model field (including external_id, external_source, deleted_at, etc.). This is inconsistent with ProjectLiteSerializer, which uses an explicit field list and excludes computed fields like total_members. Define an explicit minimal field set (e.g., id, name, start_date, end_date, description, project, workspace, owned_by) to match the "lite" contract and prevent accidental exposure of future model fields.

🤖 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/views/cycle.py` around lines 360 - 399, The
CycleLiteSerializer currently serializes all Cycle model fields, contradicting
its lightweight contract. Update CycleLiteSerializer to use an explicit minimal
fields list including id, name, start_date, end_date, description, project,
workspace, and owned_by, excluding computed and internal fields and preventing
future fields from being exposed.
🤖 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/cycle.py`:
- Line 392: Update the cycle query ordering to pass the requested value through
the existing sanitize_order_by() helper before calling order_by(), preserving
"-created_at" as the default. Apply the same validation to the matching raw
request.GET order_by usage in the module and project endpoints.

In `@apps/api/plane/api/views/issue.py`:
- Around line 317-318: Update the unsupported_filters check in the issue-listing
view to detect whether “pql” or “filters” exists in request.GET, regardless of
its value. Preserve the existing 400 response for any supplied unsupported
parameter, including empty values.

In `@apps/api/plane/api/views/member.py`:
- Line 254: Align the non-existent workspace and project handling with the
documented OpenAPI responses: update the error returns in
WorkspaceMemberLiteAPIEndpoint and ProjectMemberLiteAPIEndpoint from HTTP 400 to
HTTP 404, preserving their existing not-found response payloads and schema
declarations.
- Around line 304-321: Add an explicit project existence check in the `get`
method before querying `project_members`, validating the project belongs to the
workspace identified by `slug`. Return the documented 404 response when no
matching project exists, while preserving the existing member listing behavior
for valid projects.

In `@apps/api/plane/api/views/project.py`:
- Line 416: Sanitize the lite endpoint ordering before passing it to
QuerySet.order_by(): in apps/api/plane/api/views/project.py:416, use
sanitize_order_by with PROJECT_ORDER_BY_ALLOWLIST and "-created_at" as the
default; in apps/api/plane/api/views/module.py:314, apply the same pattern with
the module-specific order-by allowlist and appropriate default, preserving
graceful fallback for invalid input.
- Around line 401-405: Update the workspace existence check in the project view
to return HTTP 404 when the Workspace lookup by slug fails, matching the 404
response declared by the project_docs schema; leave the error payload unchanged.

---

Nitpick comments:
In `@apps/api/plane/api/views/cycle.py`:
- Around line 360-399: The CycleLiteSerializer currently serializes all Cycle
model fields, contradicting its lightweight contract. Update CycleLiteSerializer
to use an explicit minimal fields list including id, name, start_date, end_date,
description, project, workspace, and owned_by, excluding computed and internal
fields and preventing future fields from being exposed.

In `@apps/api/plane/tests/contract/api/test_cycles_lite.py`:
- Around line 63-72: Update TestCyclesLite.test_paginated_and_excludes_archived
to assert each returned cycle item matches the lite response field set,
including that heavy fields such as total_members are absent. Reuse the
established field-shape assertion pattern from test_projects_lite.py while
preserving the existing pagination and archived-cycle checks.
- Line 1: Update the cycles-lite and modules-lite contract tests, specifically
their response-item assertions near the existing pagination and
archived-exclusion checks, to verify the expected lite fields are present and
computed/heavy fields are absent. Mirror the field-shape validation used by
test_returns_only_lite_fields in test_projects_lite.py, covering representative
fields such as id, name, start_date, and end_date where applicable.

In `@apps/api/plane/tests/contract/api/test_members_lite.py`:
- Around line 86-102: Extend TestProjectMembersLite with an unknown
workspace/project rejection test matching TestWorkspaceMembersLite, using the
existing URL helper and expected error status. Update test_lite_member_shape to
assert the returned member’s role equals 20, while preserving the existing
field-presence checks.

In `@apps/api/plane/tests/contract/api/test_modules_lite.py`:
- Around line 52-61: Update TestModulesLite.test_paginated_and_excludes_archived
to assert that each returned module contains exactly the expected lite response
fields, following the field-shape assertion pattern used by the related lite
contract tests. Keep the existing pagination, status, and archived-module
assertions unchanged.
🪄 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: 9947e3d4-3777-4ea9-aec7-846b5b2293b1

📥 Commits

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

📒 Files selected for processing (17)
  • apps/api/plane/api/serializers/__init__.py
  • apps/api/plane/api/serializers/member.py
  • apps/api/plane/api/serializers/project.py
  • apps/api/plane/api/urls/cycle.py
  • apps/api/plane/api/urls/member.py
  • apps/api/plane/api/urls/module.py
  • apps/api/plane/api/urls/project.py
  • apps/api/plane/api/views/__init__.py
  • apps/api/plane/api/views/cycle.py
  • apps/api/plane/api/views/issue.py
  • apps/api/plane/api/views/member.py
  • apps/api/plane/api/views/module.py
  • apps/api/plane/api/views/project.py
  • apps/api/plane/tests/contract/api/test_cycles_lite.py
  • apps/api/plane/tests/contract/api/test_members_lite.py
  • apps/api/plane/tests/contract/api/test_modules_lite.py
  • apps/api/plane/tests/contract/api/test_projects_lite.py

Cycle.objects.filter(workspace__slug=slug, project_id=project_id)
.filter(archived_at__isnull=True)
.select_related("project", "workspace", "owned_by")
.order_by(request.GET.get("order_by", "-created_at"))

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether ORDER_BY_PARAMETER has an enum constraint or any runtime validation exists.
rg -n "ORDER_BY_PARAMETER" --type=py -A5 apps/api/plane/utils/openapi.py
# Also check if FieldError is handled anywhere in the base views.
rg -n "FieldError" --type=py apps/api/plane/api/views/

Repository: makeplane/plane

Length of output: 225


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate relevant files and inspect the order_by handling plus any FieldError handling.
fd -a "cycle.py|base.py|openapi.py" apps/api/plane -t f || true
echo '--- rg sanitize_order_by ---'
rg -n "sanitize_order_by|order_by\\(" apps/api/plane/api/views apps/api/plane -g '*.py' || true
echo '--- rg FieldError ---'
rg -n "FieldError" apps/api/plane -g '*.py' || true

Repository: makeplane/plane

Length of output: 39733


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the specific view file around the reported line once located.
fd -a "cycle.py" apps -t f || true

Repository: makeplane/plane

Length of output: 659


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the cycle view around the reported line and the base exception handler.
sed -n '360,420p' /home/jailuser/git/apps/api/plane/api/views/cycle.py | cat -n
echo '--- base handler ---'
sed -n '1,220p' /home/jailuser/git/apps/api/plane/api/views/base.py | cat -n
echo '--- order sanitizer ---'
sed -n '1,220p' /home/jailuser/git/apps/api/plane/utils/order_queryset.py | cat -n

Repository: makeplane/plane

Length of output: 18406


Validate order_by before passing it to .order_by()
request.GET.get("order_by", "-created_at") still reaches .order_by() directly here, so an invalid field can raise FieldError and fall through the generic 500 path. Reuse sanitize_order_by() here; the same raw pattern also appears in the module and project endpoints.

🤖 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/views/cycle.py` at line 392, Update the cycle query
ordering to pass the requested value through the existing sanitize_order_by()
helper before calling order_by(), preserving "-created_at" as the default. Apply
the same validation to the matching raw request.GET order_by usage in the module
and project endpoints.

Comment on lines +317 to +318
unsupported_filters = [param for param in ("pql", "filters") if request.GET.get(param)]
if unsupported_filters:

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject unsupported parameters by presence, not truthiness.

request.GET.get(param) skips empty-but-supplied values, so requests such as ?pql= or ?filters= continue into normal issue listing instead of returning the documented 400. Check membership in the query parameters instead.

Proposed fix
-        unsupported_filters = [param for param in ("pql", "filters") if request.GET.get(param)]
+        unsupported_filters = [param for param in ("pql", "filters") if param in request.GET]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
unsupported_filters = [param for param in ("pql", "filters") if request.GET.get(param)]
if unsupported_filters:
unsupported_filters = [param for param in ("pql", "filters") if param in request.GET]
if unsupported_filters:
🤖 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/views/issue.py` around lines 317 - 318, Update the
unsupported_filters check in the issue-listing view to detect whether “pql” or
“filters” exists in request.GET, regardless of its value. Preserve the existing
400 response for any supplied unsupported parameter, including empty values.

),
401: UNAUTHORIZED_RESPONSE,
403: FORBIDDEN_RESPONSE,
404: WORKSPACE_NOT_FOUND_RESPONSE,

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.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

OpenAPI schema documents 404 but code returns 400 for non-existent workspace.

Both WorkspaceMemberLiteAPIEndpoint (line 254: 404: WORKSPACE_NOT_FOUND_RESPONSE) and ProjectMemberLiteAPIEndpoint (line 301: 404: PROJECT_NOT_FOUND_RESPONSE) document 404 responses, but the actual code at lines 264-268 and 311-315 returns status.HTTP_400_BAD_REQUEST. API consumers relying on the schema would expect 404 and receive 400 instead. Either change the responses to 404 (matching REST conventions for "not found") or update the schema to document 400.

Also applies to: 301-301

🤖 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/views/member.py` at line 254, Align the non-existent
workspace and project handling with the documented OpenAPI responses: update the
error returns in WorkspaceMemberLiteAPIEndpoint and ProjectMemberLiteAPIEndpoint
from HTTP 400 to HTTP 404, preserving their existing not-found response payloads
and schema declarations.

Comment on lines +304 to +321
def get(self, request, slug, project_id):
"""List project members (lite)

Retrieve a paginated, lightweight list of project members, optimized for
pickers and directories.
"""
# Check if the workspace exists
if not Workspace.objects.filter(slug=slug).exists():
return Response(
{"error": "Provided workspace does not exist"},
status=status.HTTP_400_BAD_REQUEST,
)

project_members = (
ProjectMember.objects.filter(project_id=project_id, workspace__slug=slug)
.select_related("member")
.order_by("-created_at")
)

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if ProjectMemberPermission validates project existence and what exception it raises.
rg -n "class ProjectMemberPermission" --type=py -A30 apps/api/plane/utils/permissions.py

Repository: makeplane/plane

Length of output: 229


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the permission class and the lite endpoint implementation.
git ls-files | rg 'member\.py$|permissions\.py$|project.*member|ProjectMember'
rg -n "class ProjectMemberPermission|ProjectMemberLiteAPIEndpoint|def get\(self, request, slug, project_id\)" apps/api -S

Repository: makeplane/plane

Length of output: 4263


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the lite endpoint and the matching permission class.
sed -n '280,340p' apps/api/plane/api/views/member.py
printf '\n---\n'
sed -n '1,180p' apps/api/plane/utils/permissions/project.py
printf '\n---\n'
sed -n '1,220p' apps/api/plane/app/permissions/project.py

Repository: makeplane/plane

Length of output: 12256


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether the lite endpoint uses ProjectMemberPermission and how exceptions are handled.
sed -n '240,320p' apps/api/plane/api/views/member.py
printf '\n---\n'
sed -n '1,220p' apps/api/plane/api/views/base.py
printf '\n---\n'
rg -n "DoesNotExist|handle_exception" apps/api/plane/api/views/base.py apps/api/plane/api/views -S

Repository: makeplane/plane

Length of output: 12844


Add an explicit project existence check here. ProjectMemberPermission only enforces access; it doesn’t raise on a missing project_id, so an invalid project in an existing workspace can still return 200 with an empty list instead of the documented 404.

🤖 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/views/member.py` around lines 304 - 321, Add an explicit
project existence check in the `get` method before querying `project_members`,
validating the project belongs to the workspace identified by `slug`. Return the
documented 404 response when no matching project exists, while preserving the
existing member listing behavior for valid projects.

Comment on lines +401 to +405
if not Workspace.objects.filter(slug=slug).exists():
return Response(
{"error": "Provided workspace does not exist"},
status=status.HTTP_400_BAD_REQUEST,
)

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Status code mismatch: workspace-not-found returns 400 but schema documents 404.

The @project_docs response map declares 404: WORKSPACE_NOT_FOUND_RESPONSE, but the handler returns HTTP_400_BAD_REQUEST. Clients relying on the OpenAPI contract will expect 404 and may not handle 400 correctly.

🔧 Proposed fix: return 404 to match the documented contract
         if not Workspace.objects.filter(slug=slug).exists():
             return Response(
                 {"error": "Provided workspace does not exist"},
-                status=status.HTTP_400_BAD_REQUEST,
+                status=status.HTTP_404_NOT_FOUND,
             )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if not Workspace.objects.filter(slug=slug).exists():
return Response(
{"error": "Provided workspace does not exist"},
status=status.HTTP_400_BAD_REQUEST,
)
if not Workspace.objects.filter(slug=slug).exists():
return Response(
{"error": "Provided workspace does not exist"},
status=status.HTTP_404_NOT_FOUND,
)
🤖 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/views/project.py` around lines 401 - 405, Update the
workspace existence check in the project view to return HTTP 404 when the
Workspace lookup by slug fails, matching the 404 response declared by the
project_docs schema; leave the error payload unchanged.

if not include_archived:
projects = projects.filter(archived_at__isnull=True)

projects = projects.order_by(request.GET.get("order_by", "-created_at"))

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Unsanitized order_by in lite endpoints causes 500 errors on invalid input. Both lite endpoints pass raw request.GET.get("order_by", "-created_at") directly to QuerySet.order_by(). Invalid field names raise Django FieldError, caught by BaseAPIView.handle_exception as a generic 500. The existing ProjectListCreateAPIEndpoint in the same file already uses sanitize_order_by() with an allowlist and graceful default — the lite endpoints should follow the same pattern.

  • apps/api/plane/api/views/project.py#L416: replace raw order_by with sanitize_order_by(request.GET.get("order_by", "-created_at"), PROJECT_ORDER_BY_ALLOWLIST, default="-created_at").
  • apps/api/plane/api/views/module.py#L314: apply the equivalent sanitize_order_by call with a module-appropriate allowlist.
📍 Affects 2 files
  • apps/api/plane/api/views/project.py#L416-L416 (this comment)
  • apps/api/plane/api/views/module.py#L314-L314
🤖 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/views/project.py` at line 416, Sanitize the lite endpoint
ordering before passing it to QuerySet.order_by(): in
apps/api/plane/api/views/project.py:416, use sanitize_order_by with
PROJECT_ORDER_BY_ALLOWLIST and "-created_at" as the default; in
apps/api/plane/api/views/module.py:314, apply the same pattern with the
module-specific order-by allowlist and appropriate default, preserving graceful
fallback for invalid input.

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