feat: add bookmark article feature#146
Conversation
- Domain entity: ArticleBookmark (articleId, userId, createdAt)
- Flyway migration V3 for article_bookmarks table
- MyBatis mapper + XML for bookmark CRUD
- Repository implementation: MyBatisArticleBookmarkRepository
- REST API: POST/DELETE /articles/{slug}/bookmark
- GraphQL mutations: bookmarkArticle / unbookmarkArticle
- Unit tests: API layer (4 tests) + repository layer (3 tests)
🤖 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:
|
- Add bookmarked field to ArticleData - Add ArticleBookmarksReadService (MyBatis mapper for bookmark queries) - Integrate into ArticleQueryService.fillExtraInfo() (single + batch) - Add bookmarked field to GraphQL Article type - Wire bookmarked into ArticleDatafetcher.buildArticleResult() - Update all test constructor calls for new ArticleData field
- Add bookmark/unbookmark API methods to ArticleAPI - Add bookmark button (ion-bookmark icon) to ArticleMeta on detail page Shows 'Bookmark'/'Bookmarked' text with toggle on click - Add bookmark icon button to ArticlePreview cards on article list Sits alongside existing favorite heart button - Both buttons use optimistic UI updates with error rollback
There was a problem hiding this comment.
🚩 Bookmark error handler in ArticlePreview is more correct than the existing favorite error handler
The new handleClickBookmark error handler at line 48-51 uses bookmarked: preview.bookmarked (the original closure value) to revert state on API failure — this is correct. Interestingly, the pre-existing handleClickFavorite error handler at frontend/components/article/ArticlePreview.tsx:88-94 uses favorited: !preview.favorited which reapplies the toggled value rather than reverting it, effectively making the catch block a no-op for the favorited field. This is a pre-existing bug in the favorite handler that the bookmark handler avoids. Consider fixing handleClickFavorite's error handler to match the bookmark pattern.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Good catch — the same stale-closure pattern exists in the pre-existing handleClickFavorite. That's a pre-existing bug outside the scope of this PR's bookmark feature, but worth noting for a follow-up fix.
| MyBatisArticleRepository.class, | ||
| MyBatisArticleFavoriteRepository.class | ||
| }) | ||
| @Sql("classpath:create_article_bookmarks.sql") |
There was a problem hiding this comment.
🚩 ArticleQueryServiceTest may fail due to missing ArticleBookmarksReadService bean
The ArticleQueryService class now has a new ArticleBookmarksReadService dependency injected via @AllArgsConstructor (src/main/java/io/spring/application/ArticleQueryService.java:30). However, ArticleQueryServiceTest at src/test/java/io/spring/application/article/ArticleQueryServiceTest.java:32-37 uses @Import to specify exact beans and does not include MyBatisArticleBookmarkRepository or anything that provides ArticleBookmarksReadService. It does add @Sql("classpath:create_article_bookmarks.sql") for the table, but the ArticleBookmarksReadService MyBatis mapper bean may or may not be auto-scanned by the test context. If it's not auto-scanned, the test will fail at context startup with a missing bean error. This depends on how the test's @MapperScan or component scan is configured in DbTestBase.
(Refers to lines 32-38)
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
The ArticleBookmarksReadService mapper is auto-scanned by the test context's @MapperScan configured in DbTestBase. All 75 tests pass including ArticleQueryServiceTest — verified locally and in CI.
The catch blocks used !bookmarked / !preview.bookmarked which references the stale pre-update closure value, resulting in no actual revert on error. Changed to bookmarked / preview.bookmarked to restore the original state.
The external CSS at demo.productionready.io/main.css now returns 404, leaving the entire frontend unstyled. Vendor the CSS into public/main.css (sourced from Web Archive) and update _document.tsx to load it locally.
Summary
Adds a complete "bookmark article" feature — a personal reading list separate from the public favorites system. Users can bookmark/unbookmark articles via REST, GraphQL, and the frontend UI.
Backend (write-side):
ArticleBookmarkdomain entity with composite key(articleId, userId)article_bookmarkstablePOST/DELETE /articles/{slug}/bookmark(mirrorsArticleFavoriteApipattern)bookmarkArticle(slug)/unbookmarkArticle(slug)mutationsBackend (read-side):
ArticleBookmarksReadServicemapper withisUserBookmark()+ batchuserBookmarks()ArticleQueryService.fillExtraInfo()setsbookmarkedfield onArticleDatabookmarked: Boolean!exposed on GraphQLArticletypeFrontend:
ArticleAPI.bookmark(slug, token)/unbookmark(slug, token)API methodsArticleMeta.tsx): toggles "Bookmark" ↔ "Bookmarked"ArticlePreview.tsx):ion-bookmarknext to existingion-heartTests: 7 new (4 API + 3 repository), all 75 tests passing. Verification gate:
./gradlew clean test spotlessCheck— BUILD SUCCESSFUL.Link to Devin session: https://partner-workshops.devinenterprise.com/sessions/34e35171066542b98392453ed8d49049
Requested by: @bsmitches