(backend/frontend) move access/invitation cleanup from frontend to backend on document move#2223
Conversation
Access and invitation cleanup on document move is now handled atomically by the backend. The frontend no longer needs to fetch and delete accesses/invitations after a successful move. Signed-off-by: Mohamed El Amine BOUKERFA <boukerfa.ma@gmail.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
WalkthroughThe backend DocumentViewSet.move now detects permission-scope changes (root status, sibling-root placement, or root-different moves), converts owner_accesses to a list when target is root, and—when scope changes—deletes the document's direct accesses and invitations inside the same Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/backend/core/api/viewsets.py`:
- Around line 965-971: Materialize the owner_accesses queryset before deleting
direct accesses: before calling document.accesses.all().delete() (and
document.invitations.all().delete()), evaluate owner_accesses into a concrete
list (e.g. owner_accesses = list(owner_accesses)) so the later owner-recreation
loop can iterate over the preserved items; update the code that computes
owner_accesses to materialize it and then proceed with the deletes and the owner
recreation logic.
- Around line 969-971: The cleanup condition incorrectly only compares current
roots; instead compute the prospective root after the move and use that to
decide whether to drop direct accesses/invitations. Change the logic around
document.is_root(), document.get_root(), and target_document.get_root() to
determine the new root post-move (e.g., if moving as a child use
target_document.get_root(), if moving as a sibling use
target_document.get_parent() and its root or None if that parent is root) and
then run document.accesses.all().delete() and
document.invitations.all().delete() when document.is_root() or
document.get_root() != prospective_root (i.e., when the root membership changes
after the move).
In `@src/backend/core/tests/documents/test_api_documents_move.py`:
- Around line 594-620: The test currently fails before the deletion block
because the target lookup uses a random UUID; change it to use a real target
document and force Document.move to raise after the deletes so rollback can be
observed. Concretely: create a real target_document (e.g. via
factories.DocumentFactory()) and pass its id in the POST, and monkeypatch or
temporarily stub Document.move (the method on the Document model) to raise an
exception when called; then call client.post on the move endpoint and assert the
response indicates failure and that document.accesses.count() and
document.invitations.count() remain unchanged (verifying the deletion roll
back).
🪄 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: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 56acc976-7a4f-4750-b14f-c62dbde5d92e
📒 Files selected for processing (5)
src/backend/core/api/viewsets.pysrc/backend/core/tests/documents/test_api_documents_move.pysrc/frontend/apps/impress/src/features/docs/doc-management/api/useMoveDoc.tsxsrc/frontend/apps/impress/src/features/docs/docs-grid/components/DocGridContentList.tsxsrc/frontend/apps/impress/src/features/docs/docs-grid/components/DocMoveModal.tsx
| def test_api_documents_move_scope_change_deletion_is_atomic(): | ||
| """ | ||
| When accesses/invitations are to be deleted (root or cross-tree move), both | ||
| deletions and the tree move are atomic: if the move fails, deletions roll back. | ||
| """ | ||
| user = factories.UserFactory() | ||
| client = APIClient() | ||
| client.force_login(user) | ||
|
|
||
| other_user = factories.UserFactory() | ||
| document = factories.DocumentFactory( | ||
| users=[(user, "owner"), (other_user, "editor")] | ||
| ) | ||
| factories.InvitationFactory(document=document) | ||
|
|
||
| assert document.is_root() | ||
|
|
||
| response = client.post( | ||
| f"/api/v1.0/documents/{document.id!s}/move/", | ||
| data={"target_document_id": str(uuid4())}, | ||
| ) | ||
|
|
||
| assert response.status_code == 400 | ||
|
|
||
| # Accesses and invitations must still exist due to transaction rollback | ||
| assert document.accesses.count() == 2 | ||
| assert document.invitations.count() == 1 |
There was a problem hiding this comment.
This rollback test never reaches the new deletion path.
With a random UUID, the view returns from the target lookup error branch before the document.accesses.all().delete() / document.invitations.all().delete() block runs. This still passes even if the transaction wrapper is removed. To verify rollback, force a failure after those deletes instead, e.g. by making Document.move(...) raise.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/backend/core/tests/documents/test_api_documents_move.py` around lines 594
- 620, The test currently fails before the deletion block because the target
lookup uses a random UUID; change it to use a real target document and force
Document.move to raise after the deletes so rollback can be observed.
Concretely: create a real target_document (e.g. via factories.DocumentFactory())
and pass its id in the POST, and monkeypatch or temporarily stub Document.move
(the method on the Document model) to raise an exception when called; then call
client.post on the move endpoint and assert the response indicates failure and
that document.accesses.count() and document.invitations.count() remain unchanged
(verifying the deletion roll back).
When a document is moved outside its current permission scope (root document, cross-tree move, or promotion to root), its direct accesses and pending invitations are now deleted server-side within the same atomic transaction as the move itself. This ensures consistency: if the move fails, deletions are rolled back. Signed-off-by: Mohamed El Amine BOUKERFA <boukerfa.ma@gmail.com>
0c0f025 to
99bfe78
Compare
Purpose
Move the access and invitation cleanup logic from the frontend to the backend during document move operations.
Problems with the current approach:
an inconsistent state.
breaking the inheritance model.
Screen.Recording.2026-04-16.at.17.29.32.mov
Proposal