Skip to content

PATCH /editor/project/visibility returns 200 with null and doesn't update when projectId is a slug #4348

Open

Description

@Prbhtsgh

p5.js version

No response

What is your operating system?

Mac OS

Web browser and version

Google Chrome 153.0.8010.53 (Official Build) (arm64)

Actual Behavior

PATCH /editor/project/visibility looks up the project by either _id or slug, but then updates it by _id only. When a slug is sent as projectId, the ownership check passes, but the update matches no document. The visibility is not changed, and the server still responds 200 OK with null as the body.

In changeProjectVisibility (server/controllers/project.controller.js):

  • The lookup uses Project.findOne({ $or: [{ _id: projectId }, { slug: projectId }] }), which finds the project by slug.
  • The ownership check runs on that project and passes.
  • The update uses Project.findByIdAndUpdate(projectId, ...). With a slug, this searches for _id === "<slug>", matches nothing and returns null.
  • res.status(200).json(updatedProject) then sends null with status 200.

export async function changeProjectVisibility(req, res) {
try {
const { projectId, visibility: newVisibility } = req.body;
const project = await Project.findOne({
$or: [{ _id: projectId }, { slug: projectId }]
});
if (!project) {
return res
.status(404)
.json({ success: false, message: 'No project found.' });
}
if (!project.user.equals(req.user._id)) {
return res
.status(403)
.json({ success: false, message: 'Unauthorized action.' });
}
if (newVisibility !== 'Private' && newVisibility !== 'Public') {
return res.status(400).json({ success: false, message: 'Invalid data.' });
}
const updatedProject = await Project.findByIdAndUpdate(
projectId,
{
visibility: newVisibility
},
{
new: true,
runValidators: true
}
)
.populate('user', 'username')
.exec();
return res.status(200).json(updatedProject);
} catch (error) {
return res.status(500).json(error);
}
}

The editor UI always sends the _id, so regular users don't hit this. It affects direct calls to the endpoint.

Checked related issues #3864 / #4291 / #3875 and PRs #3920 / #4305 / #3893. #3920 and #4305 change only client-side visibility state, and #3893 only restricts fields in updateProject. None of them change this lookup/update mismatch.

Expected Behavior

The update should apply to the same project that was found and ownership-checked, so sending a slug changes the visibility just like sending the _id. Alternatively, if slugs are not meant to be supported here, the endpoint should reject them with a 4xx error instead of responding 200 with null.

Steps to reproduce

Steps:

Reproduced on the latest develop branch, running locally with Docker.

  1. Log in, create a new sketch named visibility test, and save it. Its slug is visibility_test. You can confirm it in Mongo:
    db.projects.find({ name: 'visibility test' }, { slug: 1, visibility: 1 })
    In my case the visibility was Private.
  2. Toggle the sketch's visibility once from the toolbar, and copy the PATCH /editor/project/visibility request from DevTools (Network tab → Copy as cURL).
  3. Resend it with the body {"projectId":"visibility_test","visibility":"Public"}.
    • Response: 200 OK with the body null
    • In Mongo, the visibility is still Private (unchanged)
  4. Resend it with the body {"projectId":"<the sketch _id>","visibility":"Public"}.
    • Response: 200 OK with the full project JSON
    • In Mongo, the visibility is now Public

The only difference between steps 3 and 4 is sending the slug instead of the _id.

Possible fix

Update the project that was already found and checked, instead of looking it up again by the raw projectId:

const updatedProject = await Project.findByIdAndUpdate(
  project._id,
  { visibility: newVisibility },
  { new: true, runValidators: true }
)

I'd be happy to open a PR for this if the approach looks good.

Activity

  1. ANUBHAVSINGH30 commented on Oct 8, 2026

    @ANUBHAVSINGH30

    Hi @Prbhtsgh, I'd like to work on this issue.

    I've gone through the reproduction and the lookup/update mismatch makes sense: the project is correctly resolved by either _id or slug, but the update uses the raw projectId, causing a slug-based request to return 200 with null without updating anything.

    The proposed approach of updating using the already-resolved project._id looks straightforward and avoids doing a second lookup.

    If that's okay, I'd be happy to open a PR for this.

  2. Prbhtsgh commented on Oct 8, 2026

    @Prbhtsgh
    Author

    Hi @ANUBHAVSINGH30 , thanks for taking a look and for confirming the analysis!

    As mentioned in the issue, I'd like to work on this one myself. I've already prepared the fix along with a unit test (passes with the fix, fails without it) locally, and I'm waiting for maintainer approval before opening a PR, per the contributing guidelines.

    I'll leave the assignment to the maintainers

  3. ANUBHAVSINGH30 commented on Oct 8, 2026

    @ANUBHAVSINGH30

    No worries, thanks for clarifying! Good luck with the PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Awaiting Maintainer ApprovalNeeds review from a maintainer before moving forwardBugError or unexpected behaviors

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions