MTM-4: shared per-article bookmarked read-model flag (REST + GraphQL)#183
Conversation
🤖 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:
|
| createdAt: String! | ||
| description: String! | ||
| favorited: Boolean! | ||
| bookmarked: Boolean! |
There was a problem hiding this comment.
🚩 No bookmark/unbookmark API endpoints exist yet
The PR adds read-side support for bookmarks (querying whether an article is bookmarked, exposing the flag in REST/GraphQL responses), but there are no REST controller endpoints or GraphQL mutations to actually create or remove bookmarks. The domain model (ArticleBookmarkRepository with save/find/remove) and the persistence layer (MyBatisArticleBookmarkRepository, ArticleBookmarkMapper.xml) are fully wired, but without API endpoints, the bookmarked field will always be false in production until a follow-up PR adds the write-side. This may be intentional (incremental delivery), but worth confirming.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Intentional — incremental delivery. MTM-4 owns the shared read-side bookmarked flag (decision D5); the bookmark add/remove write endpoints are delivered by the parallel MTM-2 story and were explicitly out of scope here (the spec said not to depend on them). State is driven via ArticleBookmarkRepository in tests to verify the flag flips. Once MTM-2 is integrated, the flag will reflect real bookmarks end-to-end.
Summary
Implements MTM-4 (epic MTM-1 "Reading List"): exposes whether the current user has bookmarked an article, via a shared per-article
bookmarkedread-model flag. Built on the MTM-6 persistence foundation (ArticleBookmarksReadService). Mirrors the existingfavoritedflag exactly. Per decision D5, this story owns the flag; MTM-2/MTM-3 deliberately don't touchArticleData/ArticleQueryService/SDL.MTM-19 — read-model flag + fill logic
ArticleData: addprivate boolean bookmarked;(default false), placed next tofavorited. Lombok@Data/@AllArgsConstructor⇒ serializes like the other booleans.ArticleQueryService: injectArticleBookmarksReadService; set the flag in both fill paths, mirroring favorited:fillExtraInfo(id, user, data)→data.setBookmarked(readSvc.isUserBookmark(user.getId(), id))setIsBookmarked(articles, currentUser)usingreadSvc.userBookmarks(ids, currentUser)user == null): extra info isn't filled ⇒ staysfalse.MTM-20 — surface on both APIs
ArticleDatais returned (single, list, feed).bookmarked: Boolean!totype Article;buildArticleResult(...)→.bookmarked(articleData.isBookmarked()). Non-null is always satisfied (default false).MTM-21 — tests (state driven via
ArticleBookmarkRepository, not the MTM-2 endpoints)ArticleQueryServiceTest, real DB): true when current user bookmarked, false otherwise; single + list + cursor-list + feed; anonymous → false; flips on add/remove; only current user's bookmark counts; independent offavorited.bookmarkedpresent inGET /articles/{slug}(true & false) and inGET /articles/GET /articles/feed.ArticleDatafetcherTest): true/false on single article, list, feed; anonymous → false.Note: the test profile runs only Flyway V1 (
spring.flyway.target=1), soArticleQueryServiceTestcreates the V3article_bookmarkstable in@BeforeEach(same approach as the MTM-6 read-service test).Verification gate
./gradlew clean build -x test— BUILD SUCCESSFUL./gradlew clean test— BUILD SUCCESSFUL (all green)./gradlew spotlessCheck— passes (spotlessApplyrun before commit)ArticleData100%,ArticleQueryService85%, new datafetcher mapping covered.jacocoTestCoverageVerificationenforces a bundle-wide 80% min that is pre-existing-failing (basefeat/reading-list= 0.33); this change raises it to 0.37. Not a new-code regression.Files changed
application/data/ArticleData.java,application/ArticleQueryService.javaresources/schema/schema.graphqls,graphql/ArticleDatafetcher.javaArticleQueryServiceTest,ListArticleApiTest,ArticleApiTest, newArticleDatafetcherTest; constructor-call updates inTestHelper,ArticlesApiTest,ArticleFavoriteApiTest.Link to Devin session: https://partner-workshops.devinenterprise.com/sessions/d7530b267fd34e53bd25fa4d255bcffe