Skip to content

feat: persist no-ai feed preference#3773

Merged
nimrodkra merged 6 commits intomainfrom
feat/no-ai-feed-preference
Apr 3, 2026
Merged

feat: persist no-ai feed preference#3773
nimrodkra merged 6 commits intomainfrom
feat/no-ai-feed-preference

Conversation

@nimrodkra
Copy link
Copy Markdown
Member

Summary

  • add a persisted noAiFeedEnabled settings flag to the settings GraphQL contract
  • apply No AI filtering in the v2 authenticated feed when either the query arg or saved preference is enabled
  • cover the saved settings mutation path and the saved-preference feed path in API tests

Test plan

  • pnpm run db:migrate:reset
  • NODE_ENV=test npx jest __tests__/settings.ts --testEnvironment=node --runInBand
  • NODE_ENV=test npx jest __tests__/feeds.ts --testEnvironment=node --runInBand -t "should include no-ai blocked tags and title words"
  • pnpm run build

Made with Cursor

Store the No AI preference in user settings and apply it in the v2 feed path without persisting large blocked filter lists.

Made-with: Cursor
@pulumi
Copy link
Copy Markdown

pulumi Bot commented Apr 2, 2026

🍹 The Update (preview) for dailydotdev/api/prod (at 6552c6c) was successful.

✨ Neo Explanation

Routine production deployment promoting commit `85895836`: all services and cron jobs get the new image, and fresh DB + ClickHouse migration Jobs are queued to run. ✅ Low Risk — assuming the migrations in this commit are backward-compatible.

This is a standard application release, rolling out commit 85895836 to replace c4b070d1 across all deployments and cron jobs. Every change is purely an image tag and version label bump — no configuration, resource limits, environment variables, or scheduling changes are involved.

The two migration Jobs (Postgres via TypeORM and ClickHouse) follow the expected pattern: old commit-tagged Jobs are deleted and new ones for the incoming commit are created. These Jobs run migration:run against the live database, so the migrations in this commit are what carry any real risk — the infrastructure change itself is safe.

🔵 Info — The GCP provider warning about missing default credentials is cosmetic and did not block the update; it relates to a region-list lookup, not to any resource being deployed.

Resource Changes

    Name                                                   Type                           Operation
~   vpc-native-channel-digests-cron                        kubernetes:batch/v1:CronJob    update
~   vpc-native-update-views-cron                           kubernetes:batch/v1:CronJob    update
~   vpc-native-expire-super-agent-trial-cron               kubernetes:batch/v1:CronJob    update
~   vpc-native-calculate-top-readers-cron                  kubernetes:batch/v1:CronJob    update
~   vpc-native-clean-zombie-images-cron                    kubernetes:batch/v1:CronJob    update
-   vpc-native-api-clickhouse-migration-c4b070d1           kubernetes:batch/v1:Job        delete
~   vpc-native-clean-expired-better-auth-sessions-cron     kubernetes:batch/v1:CronJob    update
~   vpc-native-personalized-digest-cron                    kubernetes:batch/v1:CronJob    update
~   vpc-native-clean-channel-highlights-cron               kubernetes:batch/v1:CronJob    update
~   vpc-native-update-trending-cron                        kubernetes:batch/v1:CronJob    update
~   vpc-native-check-analytics-report-cron                 kubernetes:batch/v1:CronJob    update
~   vpc-native-rotate-weekly-quests-cron                   kubernetes:batch/v1:CronJob    update
~   vpc-native-user-profile-analytics-clickhouse-cron      kubernetes:batch/v1:CronJob    update
~   vpc-native-clean-zombie-opportunities-cron             kubernetes:batch/v1:CronJob    update
~   vpc-native-update-tags-str-cron                        kubernetes:batch/v1:CronJob    update
~   vpc-native-update-achievement-rarity-cron              kubernetes:batch/v1:CronJob    update
~   vpc-native-clean-zombie-users-cron                     kubernetes:batch/v1:CronJob    update
~   vpc-native-worker-job-deployment                       kubernetes:apps/v1:Deployment  update
~   vpc-native-generate-search-invites-cron                kubernetes:batch/v1:CronJob    update
~   vpc-native-private-deployment                          kubernetes:apps/v1:Deployment  update
~   vpc-native-deployment                                  kubernetes:apps/v1:Deployment  update
~   vpc-native-clean-stale-user-transactions-cron          kubernetes:batch/v1:CronJob    update
~   vpc-native-post-analytics-history-day-clickhouse-cron  kubernetes:batch/v1:CronJob    update
~   vpc-native-channel-highlights-cron                     kubernetes:batch/v1:CronJob    update
~   vpc-native-user-posts-analytics-refresh-cron           kubernetes:batch/v1:CronJob    update
-   vpc-native-api-db-migration-c4b070d1                   kubernetes:batch/v1:Job        delete
~   vpc-native-temporal-deployment                         kubernetes:apps/v1:Deployment  update
~   vpc-native-user-profile-updated-sync-cron              kubernetes:batch/v1:CronJob    update
+   vpc-native-api-db-migration-85895836                   kubernetes:batch/v1:Job        create
~   vpc-native-rotate-daily-quests-cron                    kubernetes:batch/v1:CronJob    update
~   vpc-native-update-highlighted-views-cron               kubernetes:batch/v1:CronJob    update
~   vpc-native-validate-active-users-cron                  kubernetes:batch/v1:CronJob    update
~   vpc-native-update-source-public-threshold-cron         kubernetes:batch/v1:CronJob    update
~   vpc-native-update-current-streak-cron                  kubernetes:batch/v1:CronJob    update
~   vpc-native-sync-subscription-with-cio-cron             kubernetes:batch/v1:CronJob    update
~   vpc-native-generic-referral-reminder-cron              kubernetes:batch/v1:CronJob    update
~   vpc-native-post-analytics-clickhouse-cron              kubernetes:batch/v1:CronJob    update
~   vpc-native-clean-zombie-user-companies-cron            kubernetes:batch/v1:CronJob    update
~   vpc-native-ws-deployment                               kubernetes:apps/v1:Deployment  update
~   vpc-native-squad-posts-analytics-refresh-cron          kubernetes:batch/v1:CronJob    update
~   vpc-native-personalized-digest-deployment              kubernetes:apps/v1:Deployment  update
... and 11 other changes

@nimrodkra
Copy link
Copy Markdown
Member Author

@claude please review this pr

@claude
Copy link
Copy Markdown

claude Bot commented Apr 2, 2026

Claude finished @nimrodkra's task in 2m 44s —— View job


PR Review

  • Gather context
  • Read changed files
  • Analyze and provide review feedback

Overall this is a clean, well-scoped implementation. The entity, GraphQL schema, and mutation paths all follow existing patterns correctly. A few things worth flagging:


Read replica not used in isSavedNoAiEnabled

src/schema/feeds.ts:1396-1402

const isSavedNoAiEnabled = async (ctx: AuthContext): Promise<boolean> => {
  const settings = await ctx.con.getRepository(Settings).findOneBy({
    userId: ctx.userId,
  });
  return settings?.flags?.noAiFeedEnabled ?? false;
};

ctx.con is the primary DataSource. Per the project guidelines, GraphQL Query resolvers should prefer the read replica for eventual-consistent reads. This is a pure read with no write dependency, so it should use queryReadReplica:

import { queryReadReplica } from '../common/queryReadReplica';

const isSavedNoAiEnabled = async (ctx: AuthContext): Promise<boolean> => {
  const settings = await queryReadReplica(ctx.con, ({ queryRunner }) =>
    queryRunner.manager.getRepository(Settings).findOneBy({
      userId: ctx.userId,
    }),
  );
  return settings?.flags?.noAiFeedEnabled ?? false;
};

Fix this →


Duplicate shouldApplyNoAi computation across ranking branches

src/schema/feeds.ts:1603-1635

isSavedNoAiEnabled(ctx) is computed identically in both the POPULARITY and TIME branches. While only one executes per request (they're distinct if blocks guarded by args.version >= 2), both conditions share the same gate. The DB call can be hoisted to avoid any future confusion if branches are added:

feed: async (source, args: ConfiguredFeedArgs, ctx: AuthContext, info) => {
  if (args.version >= 2) {
    const shouldApplyNoAi = args.noAi || (await isSavedNoAiEnabled(ctx));
    const wrapNoAi = (gen: FeedGenerator) =>
      shouldApplyNoAi ? wrapGeneratorWithNoAi(gen) : gen;

    if (args.ranking === Ranking.POPULARITY) {
      return feedResolverCursor(source, {
        ...(args as FeedArgs),
        generator: wrapNoAi(versionToFeedGenerator(args.version)),
      }, ctx, info);
    }
    if (args.ranking === Ranking.TIME) {
      return feedResolverCursor(source, {
        ...(args as FeedArgs),
        generator: wrapNoAi(versionToTimeFeedGenerator(args.version)),
      }, ctx, info);
    }
  }
  return feedResolverV1(source, args, ctx, info);
},

Missing test coverage for TIME ranking

__tests__/feeds.ts

The new test only verifies that the saved preference is applied when using the default POPULARITY ranking (version: 20 without explicit ranking). There's no coverage for Ranking.TIME with the saved preference. Given the two branches are now largely equivalent, a single test for each would give better confidence they both read the setting correctly.


Minor: transformSettingFlags default order

src/common/flags.ts:11

The explicit noAiFeedEnabled: flags?.noAiFeedEnabled ?? false is set before ...flags, then overwritten by the spread. This matches the existing pattern for other flags (e.g., clickbaitShieldEnabled) so it's consistent — just noting it works correctly because the spread wins when the key exists, and the default wins when it doesn't.


What's good

  • Entity, SettingsFlagsPublic, SETTINGS_DEFAULT, GraphQL types, and snapshot — all consistently updated.
  • No DB migration needed (JSONB column, new key only).
  • ctx type correctly tightened from ContextAuthContext since feed has @auth.
  • Settings mutation uses updateFlagsStatement (existing atomic JSONB merge), so noAiFeedEnabled flows through without any extra wiring.
  • Test verifies both the mutation path and the feed-apply path end-to-end.

Use the read replica for the saved no-ai feed flag and cover the TIME ranking path so the v2 resolver behaves consistently across both feed generators.

Made-with: Cursor
@nimrodkra
Copy link
Copy Markdown
Member Author

Addressed the latest review feedback: the saved No AI setting now reads from the replica, the duplicated resolver branch logic was tightened up, and TIME ranking now has explicit coverage in the feed tests.

Reformat the No AI generator helper in feeds.ts so the API PR matches CircleCI's prettier lint expectations.

Made-with: Cursor
@nimrodkra nimrodkra requested review from capJavert and idoshamun April 2, 2026 22:12
Comment thread src/schema/feeds.ts
@nimrodkra nimrodkra enabled auto-merge (squash) April 3, 2026 18:00
@nimrodkra nimrodkra disabled auto-merge April 3, 2026 18:00
@nimrodkra nimrodkra merged commit 235e284 into main Apr 3, 2026
10 checks passed
@nimrodkra nimrodkra deleted the feat/no-ai-feed-preference branch April 3, 2026 18:07
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.

2 participants