Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions src/common/enums/quote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,3 @@ export const QUOTE_STATE = {

// a quote is a "one-glance" excerpt; longer text is a paragraph, not a quote
export const MAX_QUOTE_LENGTH = 80

// anti-abuse caps (product-decided defaults, tunable)
export const QUOTE_DAILY_LIMIT = 5
export const QUOTE_PER_ARTICLE_LIMIT = 2
35 changes: 3 additions & 32 deletions src/mutations/quote/putQuote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,10 @@ import {
ARTICLE_STATE,
MAX_QUOTE_LENGTH,
NODE_TYPES,
QUOTE_DAILY_LIMIT,
QUOTE_PER_ARTICLE_LIMIT,
QUOTE_STATE,
USER_STATE,
} from '#common/enums/index.js'
import {
ActionLimitExceededError,
ArticleNotFoundError,
ForbiddenByStateError,
ForbiddenError,
Expand All @@ -32,7 +29,7 @@ const resolver: GQLMutationResolvers['putQuote'] = async (
dataSources: {
atomService,
articleService,
connections: { knexRO, redis },
connections: { redis },
},
}
) => {
Expand Down Expand Up @@ -127,34 +124,8 @@ const resolver: GQLMutationResolvers['putQuote'] = async (
throw new UserInputError('this quote is already on the wall')
}

// per-article cap (prevents plastering the wall with one article)
const perArticleCount = await atomService.count({
table: 'quote',
where: {
userId: viewer.id,
articleId: article.id,
state: QUOTE_STATE.active,
},
})
if (perArticleCount >= QUOTE_PER_ARTICLE_LIMIT) {
throw new ActionLimitExceededError(
`up to ${QUOTE_PER_ARTICLE_LIMIT} quotes per article`
)
}

// daily cap; counts posting actions (UTC day), retraction does not refund
const todayStart = new Date()
todayStart.setUTCHours(0, 0, 0, 0)
const dailyCount = await knexRO('quote')
.count('id')
.where({ userId: viewer.id })
.andWhere('createdAt', '>=', todayStart)
.first()
if (Number(dailyCount?.count ?? 0) >= QUOTE_DAILY_LIMIT) {
throw new ActionLimitExceededError(
`up to ${QUOTE_DAILY_LIMIT} quotes per day`
)
}
// no quantity caps: per-article and daily limits removed, so users may post
// any number of quotes; the content rules and dedup above still apply

let quote
try {
Expand Down
18 changes: 7 additions & 11 deletions src/types/__test__/2/quote.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import {
USER_STATE,
CAMPAIGN_STATE,
QUOTE_STATE,
QUOTE_DAILY_LIMIT,
QUOTE_PER_ARTICLE_LIMIT,
} from '#common/enums/index.js'
import { toGlobalId, fromGlobalId } from '#common/utils/index.js'
import { AtomService, CampaignService } from '#connectors/index.js'
Expand Down Expand Up @@ -255,11 +253,10 @@ describe('putQuote', () => {
expect(count).toBe(1)
})

test('per-article cap blocks the next quote once the limit is reached', async () => {
// pre-fill up to the per-article limit with distinct excerpts
test('no per-article cap: more quotes from the same article still succeed', async () => {
// quantity limits removed — distinct excerpts from one article all post
await seedQuote({ content: 'some' })
await seedQuote({ content: 'html' })
expect(QUOTE_PER_ARTICLE_LIMIT).toBe(2)

const server = await testClient({
connections,
Expand All @@ -270,13 +267,12 @@ describe('putQuote', () => {
query: PUT_QUOTE,
variables: { input: { articleId: articleGlobalId, content: 'string' } },
})
expect(errors?.[0].extensions.code).toBe('ACTION_LIMIT_EXCEEDED')
expect(errors).toBeUndefined()
})

test('daily cap blocks the next quote once the limit is reached', async () => {
// seed the daily limit worth of quotes for this user (today, any article)
expect(QUOTE_DAILY_LIMIT).toBe(5)
for (let i = 0; i < QUOTE_DAILY_LIMIT; i++) {
test('no daily cap: posting beyond the old daily limit still succeeds', async () => {
// quantity limits removed — posting many in a day no longer blocks
for (let i = 0; i < 5; i++) {
await seedQuote({ content: `daily-${i}` })
}
const server = await testClient({
Expand All @@ -288,7 +284,7 @@ describe('putQuote', () => {
query: PUT_QUOTE,
variables: { input: { articleId: articleGlobalId, content: 'string' } },
})
expect(errors?.[0].extensions.code).toBe('ACTION_LIMIT_EXCEEDED')
expect(errors).toBeUndefined()
})
})

Expand Down
Loading