Skip to content

MTM-4: shared per-article bookmarked read-model flag (REST + GraphQL)#183

Merged
devin-ai-integration[bot] merged 1 commit into
feat/reading-listfrom
devin/1782443033-mtm-4-bookmarked-flag
Jun 26, 2026
Merged

MTM-4: shared per-article bookmarked read-model flag (REST + GraphQL)#183
devin-ai-integration[bot] merged 1 commit into
feat/reading-listfrom
devin/1782443033-mtm-4-bookmarked-flag

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jun 26, 2026

Copy link
Copy Markdown

Summary

Implements MTM-4 (epic MTM-1 "Reading List"): exposes whether the current user has bookmarked an article, via a shared per-article bookmarked read-model flag. Built on the MTM-6 persistence foundation (ArticleBookmarksReadService). Mirrors the existing favorited flag exactly. Per decision D5, this story owns the flag; MTM-2/MTM-3 deliberately don't touch ArticleData/ArticleQueryService/SDL.

MTM-19 — read-model flag + fill logic

  • ArticleData: add private boolean bookmarked; (default false), placed next to favorited. Lombok @Data/@AllArgsConstructor ⇒ serializes like the other booleans.
  • ArticleQueryService: inject ArticleBookmarksReadService; set the flag in both fill paths, mirroring favorited:
    • single: fillExtraInfo(id, user, data)data.setBookmarked(readSvc.isUserBookmark(user.getId(), id))
    • batch: setIsBookmarked(articles, currentUser) using readSvc.userBookmarks(ids, currentUser)
    • anonymous (user == null): extra info isn't filled ⇒ stays false.

MTM-20 — surface on both APIs

  • REST: field serializes automatically wherever ArticleData is returned (single, list, feed).
  • GraphQL: add bookmarked: Boolean! to type Article; buildArticleResult(...).bookmarked(articleData.isBookmarked()). Non-null is always satisfied (default false).

MTM-21 — tests (state driven via ArticleBookmarkRepository, not the MTM-2 endpoints)

  • Service (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 of favorited.
  • REST: bookmarked present in GET /articles/{slug} (true & false) and in GET /articles / GET /articles/feed.
  • GraphQL (ArticleDatafetcherTest): true/false on single article, list, feed; anonymous → false.

Note: the test profile runs only Flyway V1 (spring.flyway.target=1), so ArticleQueryServiceTest creates the V3 article_bookmarks table 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 (spotlessApply run before commit)
  • New-code coverage: ArticleData 100%, ArticleQueryService 85%, new datafetcher mapping covered.
  • jacocoTestCoverageVerification enforces a bundle-wide 80% min that is pre-existing-failing (base feat/reading-list = 0.33); this change raises it to 0.37. Not a new-code regression.

Files changed

  • application/data/ArticleData.java, application/ArticleQueryService.java
  • resources/schema/schema.graphqls, graphql/ArticleDatafetcher.java
  • tests: ArticleQueryServiceTest, ListArticleApiTest, ArticleApiTest, new ArticleDatafetcherTest; constructor-call updates in TestHelper, ArticlesApiTest, ArticleFavoriteApiTest.

Link to Devin session: https://partner-workshops.devinenterprise.com/sessions/d7530b267fd34e53bd25fa4d255bcffe


Open in Devin Review

@mbatchelor81 mbatchelor81 self-assigned this Jun 26, 2026
@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, CI, and merge conflict monitoring

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

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.

Devin Review found 1 potential issue.

Open in Devin Review

createdAt: String!
description: String!
favorited: Boolean!
bookmarked: Boolean!

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.

🚩 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.

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.

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.

@devin-ai-integration devin-ai-integration Bot merged commit 9df84e1 into feat/reading-list Jun 26, 2026
3 checks passed
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