feat: add delete user account (DELETE /api/user) with cascading cleanup#602
feat: add delete user account (DELETE /api/user) with cascading cleanup#602devin-ai-integration[bot] wants to merge 2 commits into
Conversation
- 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 EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
| commentMapper.deleteByUserId(userId); | ||
| articleFavoriteMapper.deleteByUserId(userId); | ||
| articleFavoriteMapper.deleteByArticleUserId(userId); | ||
| articleMapper.deleteArticleTagsByUserId(userId); | ||
| articleMapper.deleteByUserId(userId); |
There was a problem hiding this comment.
🔴 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 favoritesarticleFavoriteMapper.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
deleteByArticleUserIdequivalent 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.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
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>
Summary
Implements a Delete Account feature that allows authenticated users to delete their own account via:
DELETE /api/user— returns204 No Contenton success,401if unauthenticateddeleteUsermutation — returnsDeletionStatus { success: true }The deletion cascades through all user-related data in the correct order:
All cascading deletions are wrapped in a
@Transactionalblock to ensure atomicity.Changes across layers
UserRepositoryvoid remove(User user)MyBatisUserRepositoryremove()with cascading deletes via all mappersUserMapper,ArticleMapper,CommentMapper,ArticleFavoriteMapper(Java + XML)delete,deleteByUserId,deleteByArticleUserId, and related bulk-delete operationsUserServiceremoveUser(User user)CurrentUserApi@DeleteMappinghandlerschema.graphqls,UserMutationdeleteUsermutationCurrentUserApiTestMyBatisUserRepositoryTestReview & Testing Checklist for Human
DELETE /api/userwith a valid JWT token and confirm the user and all related data are removedDELETE /api/userwithout a token and confirm a401response./gradlew testto confirm all 71 tests passtagstable since they may be sharedNotes
spotlessJavaApplyformatter task fails on master as well due to agoogle-java-format/ Java 17 compatibility issue — this is pre-existing and unrelated to this PR.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