feat(quote): quote wall data layer for campaigns#4842
Merged
Conversation
…ssion board - new COMMENT_TYPE campaignDiscussion bound to campaign via targetId/targetTypeId - putComment: accept campaignId; only succeeded participants (campaign_user.state) or campaign organizers/managers may comment; cap content at 240 chars - WritingChallenge.discussion / discussionCount: public read resolvers - Comment.node resolves campaign comments to WritingChallenge - campaignService.isParticipant helper Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- new quote table (content, article_id, campaign_id, user_id, state) +
entity_type row; soft-delete states active/archived/banned
- putQuote mutation with full rule set:
* quote must be an excerpt of the article (primary anti-abuse: no free
typing; whitespace-normalized substring check against article content)
* 80-char cap; license gate (ARR -> author only)
* campaign-scoped: only campaign articles can be quoted onto the wall
* caps: 5/day per user, 2 per article per user, exact-duplicate rejection
- deleteQuote (retraction): poster, source article author, or admin;
soft delete, daily quota not refunded
- WritingChallenge.quotes (random sampling for shuffle) + quoteCount
- Quote type resolvers (article / poster via dataloaders)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- codegen: add Quote mapper so resolver parent is the DB model - regenerate schema.graphql / schema.d.ts (quote, campaignDiscussion) - putComment: guard article/moment notification blocks with targetAuthor - lint:fix import order in resolver index files Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## develop #4842 +/- ##
===========================================
- Coverage 72.82% 72.01% -0.82%
===========================================
Files 1054 1064 +10
Lines 21263 21089 -174
Branches 4671 4588 -83
===========================================
- Hits 15485 15187 -298
+ Misses 5706 5407 -299
- Partials 72 495 +423 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…nd id validation - add partial unique index on quote (user_id, article_id, content) for active quotes as a concurrency backstop, and surface PG unique violation as UserInputError in putQuote - order campaign_article lookup by created_at desc so attribution is deterministic when an article belongs to multiple campaigns - validate fromGlobalId type in putQuote and deleteQuote Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…uote - add `after` cursor to QuotesInput and wire the quotes resolver to the shared offset pagination util; `after` is ignored under `random` - make deleteQuote idempotent: an already-retracted quote returns success, with the permission check kept ahead of the idempotent path so it cannot be used to probe quote existence Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cover the quote-wall feature's highest-regression-risk paths: dedupe, per-article and daily caps, deleteQuote permission matrix (without leaking existence of archived quotes), idempotent retraction, and the campaign quotes query (active-only filtering, after pagination, random). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jun 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
這是什麼
金句牆(Memo Wall)的資料層與站內 API:讀者在文章頁選一句金句「上牆」,金句存進資料庫,活動頁的金句牆與(未來)博物館從這裡讀取。階段 3,承接金句產生器(前端 #5965)。
前端對應分支:
yingshinlee/matters-web的feat/quote-wall(依賴本 PR 的 schema)。改動
db/migrations/*_create_quote_table.js(新)quote表(content / article_id / campaign_id / user_id / state)+ entity_type;軟刪除狀態 active / archived / bannedsrc/common/enums/quote.ts(新)QUOTE_STATE、MAX_QUOTE_LENGTH = 80、QUOTE_DAILY_LIMIT = 5、QUOTE_PER_ARTICLE_LIMIT = 2src/definitions/quote.d.ts(新)+ index / atomService 註冊src/types/quote.ts(新)putQuote/deleteQuotemutation;WritingChallenge.quotes(隨機抽樣)/quoteCount;Quotetypesrc/mutations/quote/putQuote.ts(新)src/mutations/quote/deleteQuote.ts(新)src/queries/campaign/quotes.ts、quoteCount.ts(新)random: true時order by random()src/queries/quote/index.ts(新)putQuote 的規則(防濫用+版權)
campaign_article)才能上牆,自動歸到該活動的牆。deleteQuote(收回)
允許:貼的人、原文作者(句子的著作權在作者)、或 admin。軟刪除(state → archived,紀錄保留),呼應「不刪除,只是不再被看見」。
需 node ≥ 22 跑 codegen / typecheck(schema 有新型別)。Draft。
部署
Merge 進 develop 後先上測試站 https://matters.icu/ 驗收(建議 QA:上牆規則四種錯誤路徑、隨機抽樣、收回三種角色),再上正式站。
🤖 Generated with Claude Code