Skip to content

feat: add delete user account (DELETE /api/user) with cascading cleanup#602

Open
devin-ai-integration[bot] wants to merge 2 commits into
masterfrom
devin/1779141510-delete-user-account
Open

feat: add delete user account (DELETE /api/user) with cascading cleanup#602
devin-ai-integration[bot] wants to merge 2 commits into
masterfrom
devin/1779141510-delete-user-account

Conversation

@devin-ai-integration
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot commented May 18, 2026

Summary

Implements a Delete Account feature that allows authenticated users to delete their own account via:

  • REST: DELETE /api/user — returns 204 No Content on success, 401 if unauthenticated
  • GraphQL: deleteUser mutation — returns DeletionStatus { success: true }

The deletion cascades through all user-related data in the correct order:

  1. Comments authored by the user
  2. Comments by other users on the user's articles
  3. Article favorites made by the user
  4. Favorites on the user's articles (by other users)
  5. Article-tag relations for the user's articles
  6. Articles authored by the user
  7. Follow relations (both directions — user following others and others following user)
  8. The user record itself

All cascading deletions are wrapped in a @Transactional block to ensure atomicity.

Changes across layers

Layer File(s) Change
Core UserRepository Added void remove(User user)
Infrastructure MyBatisUserRepository Implemented remove() with cascading deletes via all mappers
Infrastructure UserMapper, ArticleMapper, CommentMapper, ArticleFavoriteMapper (Java + XML) Added delete, deleteByUserId, deleteByArticleUserId, and related bulk-delete operations
Application UserService Added removeUser(User user)
API (REST) CurrentUserApi Added @DeleteMapping handler
API (GraphQL) schema.graphqls, UserMutation Added deleteUser mutation
Tests CurrentUserApiTest Added 2 API tests (success + unauthenticated)
Tests MyBatisUserRepositoryTest Added integration test verifying cascading data cleanup (including comments by other users on deleted user's articles)

Review & Testing Checklist for Human

  • Verify the cascading deletion order handles all edge cases — particularly when a user has articles that other users have favorited or commented on
  • Test DELETE /api/user with a valid JWT token and confirm the user and all related data are removed
  • Test DELETE /api/user without a token and confirm a 401 response
  • Run the full test suite locally with ./gradlew test to confirm all 71 tests pass
  • Consider whether orphaned tags (tags only used by deleted articles) should also be cleaned up — current implementation leaves them in the tags table since they may be shared

Notes

  • The spotlessJavaApply formatter task fails on master as well due to a google-java-format / Java 17 compatibility issue — this is pre-existing and unrelated to this PR.
  • The deletion is intentionally scoped to the currently authenticated user only (no admin endpoint to delete other users).
  • Fixed review feedback: added commentMapper.deleteByArticleUserId() to also clean up comments by other users on the deleted user's articles, mirroring the existing pattern for article_favorites.

Link to Devin session: https://app.devin.ai/sessions/16300b8d913643398de793e877a2d082
Requested by: @yubin-jee


Devin Review

Status Commit
⚪ Not started

Run Devin Review

💡 Connect your GitHub account to enable automatic code reviews.

Open in Devin Review (Staging)

- Add DELETE /api/user REST endpoint for authenticated user deletion
- Add deleteUser GraphQL mutation
- Implement cascading deletion of all user-related data:
  - Comments authored by the user
  - Article favorites by the user
  - Favorites on user's articles
  - Article-tag relations for user's articles
  - Articles authored by the user
  - Follow relations (both directions)
  - User record
- Add UserRepository.remove() with @transactional support
- Add UserService.removeUser() method
- Add API tests for delete endpoint (204 on success, 401 without auth)
- Add integration test for cascading data cleanup

Co-Authored-By: Yubin Jee <yubin.jee@cognition.ai>
@devin-ai-integration
Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

Copy link
Copy Markdown
Author

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View 5 additional findings in Devin Review.

Open in Devin Review

Comment on lines +79 to +83
commentMapper.deleteByUserId(userId);
articleFavoriteMapper.deleteByUserId(userId);
articleFavoriteMapper.deleteByArticleUserId(userId);
articleMapper.deleteArticleTagsByUserId(userId);
articleMapper.deleteByUserId(userId);
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🔴 Comments by other users on deleted user's articles are not cleaned up, creating orphaned rows

The remove() method deletes comments made BY the user (commentMapper.deleteByUserId), but does not delete comments made by OTHER users on the deleted user's articles. When articleMapper.deleteByUserId(userId) later deletes the user's articles, those comments are left orphaned with article_id references pointing to non-existent articles.

Comparison with article_favorites handling

For article_favorites, the code correctly has two deletion steps:

  • articleFavoriteMapper.deleteByUserId(userId) — removes the user's own favorites
  • articleFavoriteMapper.deleteByArticleUserId(userId) — removes all favorites on the user's articles (ArticleFavoriteMapper.xml:13-15)

But for comments, only the first pattern is applied:

  • commentMapper.deleteByUserId(userId) — removes comments authored by the user
  • Missing: a deleteByArticleUserId equivalent to remove all comments on the user's articles

The orphaned comments can still be returned by CommentReadService.findById(id) (CommentReadService.xml:16-19) since that query only filters by comment id, not article id.

Prompt for agents
The remove() method in MyBatisUserRepository is missing deletion of comments by other users on the deleted user's articles. This parallels the pattern already used for article_favorites (deleteByArticleUserId).

To fix:
1. Add a new method to CommentMapper.java: void deleteByArticleUserId(@Param("userId") String userId)
2. Add a corresponding SQL in CommentMapper.xml: DELETE FROM comments WHERE article_id IN (SELECT id FROM articles WHERE user_id = #{userId})
3. Call commentMapper.deleteByArticleUserId(userId) in MyBatisUserRepository.remove(), after the existing commentMapper.deleteByUserId(userId) call and before articleMapper.deleteByUserId(userId)

This mirrors exactly what is already done for article_favorites with articleFavoriteMapper.deleteByArticleUserId(userId) at line 81.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch — fixed in 44857ee. Added commentMapper.deleteByArticleUserId(userId) to mirror the existing articleFavoriteMapper.deleteByArticleUserId() pattern. Also added a test assertion verifying comments by other users on the deleted user's articles are cleaned up.

Adds commentMapper.deleteByArticleUserId() to clean up comments left
by other users on articles owned by the deleted user, preventing
orphaned comment rows. Mirrors the existing pattern used for
article_favorites.

Co-Authored-By: Yubin Jee <yubin.jee@cognition.ai>
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.

1 participant