Skip to content

fix(api): resolve description asset URLs to workspace scope without a project - #9776

Open
seketman wants to merge 1 commit into
makeplane:previewfrom
seketman:fix/9564-asset-url-workspace-scope
Open

fix(api): resolve description asset URLs to workspace scope without a project#9776
seketman wants to merge 1 commit into
makeplane:previewfrom
seketman:fix/9564-asset-url-workspace-scope

Conversation

@seketman

@seketman seketman commented Sep 7, 2026

Copy link
Copy Markdown

Description

FileAsset.asset_url always interpolated project_id into the URL for the description entity types (ISSUE_DESCRIPTION, COMMENT_DESCRIPTION, PAGE_DESCRIPTION, DRAFT_ISSUE_DESCRIPTION), so an asset with no project resolved to /api/assets/v2/workspaces/<slug>/projects/None/<id>/, which 404s. The upload itself succeeds, so the file is in storage but the returned URL never resolves.

Those assets are not always project-bound:

  • WorkspaceFileAssetEndpoint.get_entity_id_field maps only issue_id / page_id / comment_id, never project_id, so the column stays NULL for assets created through POST /api/assets/v2/workspaces/{slug}/.
  • Workspace-level pages have no project at all — Page relates to projects through a M2M (db.ProjectPage), so there is nothing to derive.

This falls back to the workspace-scoped route when project_id is NULL, mirroring the two branches getEditorAssetSrc (packages/utils/src/editor/common.ts) already picks between on the client. Project-bound assets keep the project-scoped URL, and every other branch of the property is untouched.

Both routes already exist and both enforce access control (has_project_asset_access on WorkspaceFileAssetEndpoint), so this only makes the URL the API reports match the route the clients already use — it does not widen access.

Approaches considered and rejected

  • Reading project_id from the request body, as the public API's GenericAssetEndpoint does. WorkspaceFileAssetEndpoint is authorized at workspace level, so accepting a client-supplied project_id there would let any workspace member stamp an arbitrary project onto an asset.
  • Deriving project_id from the related entity. Not possible for pages: workspace-level pages belong to zero projects.

Note for maintainers (not addressed here)

ISSUE_ATTACHMENT interpolates project_id the same way, and DuplicateAssetEndpoint.post accepts entity_type and project_id independently, so an attachment can also end up with a NULL project_id. I left it alone because there is no workspace-scoped attachment route to fall back to, so the right behaviour there is a design call rather than a mechanical fix. Happy to follow up in a separate PR if you want it covered.

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

Screenshots and Media (if applicable)

N/A — the change is a URL-building fix in a model property.

Test Scenarios

New unit tests in apps/api/plane/tests/unit/models/test_file_asset_url.py (9 cases, run with pytest plane/tests/unit/models/test_file_asset_url.py):

  • Each of the four description entity types with project_id NULL resolves to /api/assets/v2/workspaces/{slug}/{id}/, and the URL contains no None.
  • Each of the four description entity types bound to a project keeps /api/assets/v2/workspaces/{slug}/projects/{project_id}/{id}/.
  • WORKSPACE_LOGO still resolves to the /api/assets/v2/static/{id}/ route, guarding the branch above the change.

Verified the tests fail without the fix (the 4 project-less cases go red with projects/None/ in the actual value) and pass with it. ruff check and ruff format --check both pass on the two files.

References

Fixes #9564

Summary by CodeRabbit

  • Bug Fixes
    • Fixed asset links for workspace-level descriptions so they no longer contain invalid project paths.
    • Preserved project-specific links for descriptions associated with a project.
    • Ensured workspace-level assets continue using the appropriate static links.

… project

`FileAsset.asset_url` always interpolated `project_id` into the URL for the
description entity types, so an asset with no project resolved to
`/api/assets/v2/workspaces/<slug>/projects/None/<id>/`, which 404s.

Description assets are not always project-bound: `WorkspaceFileAssetEndpoint`
never sets `project_id`, and workspace-level pages have no project at all
(`Page` relates to projects through a M2M). Fall back to the workspace-scoped
route in that case, mirroring the two branches `getEditorAssetSrc` already
picks between on the client. Project-bound assets keep the project-scoped URL.

Fixes makeplane#9564
@coldtea-pr-lens

coldtea-pr-lens Bot commented Sep 7, 2026

Copy link
Copy Markdown

◈ PR Lens

🟢 +0 new · 🟠 ~1 changed · 🔴 -0 removed · 0 flows · 2 files · commit aeb68ab


Architecture

Architecture diagram for makeplane/plane at aeb68ab

1 component touched across 4 lanes.

Open the interactive canvas


Data flow

No data-flow sequence changed in this PR.


Drill down
Application Services — 1 component
🟡 CHANGED Core Domain API (plane.app)

Contains domain models and asset URL resolution logic for project and workspace entities.


View

  • Architecture lens
  • Data flow lens
  • Expand every detail
  • Show unchanged neighbours

Tip

PR Lens is free for open source. A star on the repository is what keeps it going.

🪧 More tips
  • Run PR Lens on your own machine: npx skills add coldteadotai/pr-lens installs the agent skill. Then tell your coding agent: "Diagram the change you just made with PR Lens and attach it to the pull request."
  • Draw a diff before it is even a pull request: npx @coldtea/pr-lens-cli analyze --base origin/main reads the diff with your own model key, and npx @coldtea/pr-lens-cli render .pr-lens/graph.json draws the same lenses on your machine.
  • The boxes under View are live. Tick Architecture lens or Data flow lens to choose which diagrams appear, or Expand every detail to open every drill-down at once. The comment redraws in place a few seconds later.
  • Show unchanged neighbours lists the components this change did not touch alongside the ones it did, so the drill-down shows what the changed code sits next to.
  • GitHub will not let you zoom an image in a comment. The link under each diagram opens it on an interactive canvas, where you can zoom, pan and step through the flow.
  • The CLI's render picks up .github/pr-lens.yml automatically and applies your corrections (renames, exclusions, lane pins) at draw time.
  • Would you rather run it from CI on a key of your own? Add .github/workflows/pr-lens.yml with coldteadotai/pr-lens/packages/action@v0 and a model key in your repository secrets, say GEMINI_API_KEY. The Action asks Gemini by default, or OpenAI and any endpoint speaking /chat/completions through its provider input.
  • Push a new commit and the whole comment re-renders for the new head. An older run never overwrites a newer one, so a slow render cannot put a stale diagram back.
  • The diagrams follow your GitHub theme, so dark mode gets the dark render and light mode the light one, and the moving dots show this pull request's data in motion.

◈ Rendered by PR Lens · crafted with ❤️ by the Coldtea team · Come say hi on Discord

@CLAassistant

CLAassistant commented Sep 7, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@coderabbitai

coderabbitai Bot commented Sep 7, 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: Team

Run ID: d3a4269a-1727-44b6-97fe-647cde42c5f9

📥 Commits

Reviewing files that changed from the base of the PR and between 1fec307 and aeb68ab.

📒 Files selected for processing (2)
  • apps/api/plane/db/models/asset.py
  • apps/api/plane/tests/unit/models/test_file_asset_url.py

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

FileAsset.asset_url now avoids projects/None/ URLs for description assets without a project. Unit tests cover workspace-scoped, project-scoped, and static asset routes.

Changes

File asset URL routing

Layer / File(s) Summary
Workspace fallback for description assets
apps/api/plane/db/models/asset.py
Description assets without a project_id use the workspace-scoped route. Project-bound description assets keep the project-scoped route.
Asset URL regression coverage
apps/api/plane/tests/unit/models/test_file_asset_url.py
Tests cover issue, comment, page, and draft-issue descriptions, plus project-bound and workspace-level assets.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to aeb68

Description-image assets uploaded without a project now receive workspace-scoped URLs instead of URLs containing projects/None/, allowing them to resolve correctly while retaining existing project and workspace asset routes.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary fix: resolving description asset URLs at workspace scope when no project is present.
Description check ✅ Passed The description follows the repository template, explains the bug and solution, records the change type, documents test scenarios, and references issue #9564.
Linked Issues check ✅ Passed The PR satisfies issue #9564 by preventing projects/None/ URLs for project-less description assets while preserving project-scoped URLs for project-bound assets. The added tests cover the required URL…
Out of Scope Changes check ✅ Passed The changes are limited to the FileAsset URL fix and focused unit tests. The included draft-issue coverage is consistent with the stated description-asset behavior and does not introduce unrelated cha…
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 2 files.
✨ 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.

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.

[bug]: Uploaded issue/comment/page description images get a broken asset_url (.../projects/None/...)

2 participants