feat: community sentiment on posts - #3999
Conversation
Adds the PostCommunitySentiment type mirroring the frontend CommunitySentimentData contract plus discussion links and updatedAt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Validates the snake_case wire payload (extra.community_sentiment + extra.discussions) with Zod (lean enum, breakdown sums to 100) and maps it onto the communitySentiment column in the article and collection processors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Typed object SDL (PostCommunitySentiment + sub-types) with a jsonType graphorm mapping for the direct jsonb column. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Worker create/update/overwrite paths, Zod rejection of malformed breakdowns and lean values, GraphQL shape and null for posts without a take. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
yggdrasil serializes empty take arrays and optional strings with omitempty; requiring them in the Zod schema would drop the whole post update for any take with an empty L2 block. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
🍹 The Update (preview) for dailydotdev/api/prod (at 7a17034) was successful. Resource Changes Name Type Operation
~ vpc-native-deployment kubernetes:apps/v1:Deployment update
~ vpc-native-post-analytics-achievements-cron kubernetes:batch/v1:CronJob update
~ vpc-native-hourly-notification-cron kubernetes:batch/v1:CronJob update
+ vpc-native-api-clickhouse-migration-e5703dd2 kubernetes:batch/v1:Job create
~ vpc-native-update-views-cron kubernetes:batch/v1:CronJob update
~ vpc-native-validate-active-users-cron kubernetes:batch/v1:CronJob update
~ vpc-native-update-highlighted-views-cron kubernetes:batch/v1:CronJob update
~ vpc-native-update-source-public-threshold-cron kubernetes:batch/v1:CronJob update
~ vpc-native-post-analytics-clickhouse-cron kubernetes:batch/v1:CronJob update
~ vpc-native-clean-gifted-plus-cron kubernetes:batch/v1:CronJob update
~ vpc-native-materialize-yearly-best-post-archives-cron kubernetes:batch/v1:CronJob update
~ vpc-native-channel-digests-cron kubernetes:batch/v1:CronJob update
~ vpc-native-clean-channel-highlights-cron kubernetes:batch/v1:CronJob update
~ vpc-native-personalized-digest-cron kubernetes:batch/v1:CronJob update
- vpc-native-api-db-migration-c0f35a0e kubernetes:batch/v1:Job delete
~ vpc-native-clean-expired-better-auth-sessions-cron kubernetes:batch/v1:CronJob update
- vpc-native-api-clickhouse-migration-c0f35a0e kubernetes:batch/v1:Job delete
~ vpc-native-calculate-top-readers-cron kubernetes:batch/v1:CronJob update
~ vpc-native-daily-digest-cron kubernetes:batch/v1:CronJob update
~ vpc-native-generate-search-invites-cron kubernetes:batch/v1:CronJob update
~ vpc-native-update-achievement-rarity-cron kubernetes:batch/v1:CronJob update
~ vpc-native-post-lifecycle-state-clickhouse-cron kubernetes:batch/v1:CronJob update
~ vpc-native-check-analytics-report-cron kubernetes:batch/v1:CronJob update
~ vpc-native-expire-super-agent-trial-cron kubernetes:batch/v1:CronJob update
~ vpc-native-user-profile-analytics-clickhouse-cron kubernetes:batch/v1:CronJob update
~ vpc-native-private-deployment kubernetes:apps/v1:Deployment update
~ vpc-native-update-tags-str-cron kubernetes:batch/v1:CronJob update
~ vpc-native-worker-job-deployment kubernetes:apps/v1:Deployment update
~ vpc-native-update-trending-cron kubernetes:batch/v1:CronJob update
~ vpc-native-channel-highlights-cron kubernetes:batch/v1:CronJob update
~ vpc-native-subscription-anniversary-achievements-cron kubernetes:batch/v1:CronJob update
~ vpc-native-personalized-digest-deployment kubernetes:apps/v1:Deployment update
~ vpc-native-update-tag-materialized-views-cron kubernetes:batch/v1:CronJob update
~ vpc-native-user-profile-updated-sync-cron kubernetes:batch/v1:CronJob update
~ vpc-native-clean-zombie-opportunities-cron kubernetes:batch/v1:CronJob update
~ vpc-native-squad-posts-analytics-refresh-cron kubernetes:batch/v1:CronJob update
+ vpc-native-api-db-migration-e5703dd2 kubernetes:batch/v1:Job create
~ vpc-native-clean-zombie-users-cron kubernetes:batch/v1:CronJob update
~ vpc-native-temporal-deployment kubernetes:apps/v1:Deployment update
~ vpc-native-rotate-weekly-quests-cron kubernetes:batch/v1:CronJob update
~ vpc-native-update-current-streak-cron kubernetes:batch/v1:CronJob update
... and 15 other changes |
| mapCloudinaryUrl(post.contentHtml), | ||
| creatorTwitterImage: (post: GQLPost): GQLPost['creatorTwitterImage'] => | ||
| mapCloudinaryUrl(post.creatorTwitterImage), | ||
| communitySentiment: (post: GQLPost): GQLPost['communitySentiment'] => |
There was a problem hiding this comment.
this is not needed, we have graphorm already
There was a problem hiding this comment.
Removed in aa1c509 — the graphorm jsonType mapping already hydrates the column, the resolver was redundant.
| public async up(queryRunner: QueryRunner): Promise<void> { | ||
| await queryRunner.query(/* sql */ ` | ||
| ALTER TABLE "post" | ||
| ADD "communitySentiment" jsonb |
There was a problem hiding this comment.
Done in aa1c509 — ADD COLUMN IF NOT EXISTS / DROP COLUMN IF EXISTS.
| @Column({ nullable: true }) | ||
| readTime?: number; | ||
|
|
||
| @Column({ type: 'jsonb', nullable: true }) |
There was a problem hiding this comment.
if not specific reason, better jsonb is empty object by default, easier to update as flags then
There was a problem hiding this comment.
Kept it nullable intentionally: null is load-bearing here — GraphQL returns null for take-less posts and the apps side keys the conditional experiment enrollment off communitySentiment != null (an empty-object default would enroll everyone). We also never merge this column flag-style: yggdrasil always ships the complete take object, so whole-value replacement is the only write pattern. Happy to switch if you feel strongly, but it'd mean an is-empty check in GraphQL + apps instead of a null check.
| const communitySentiment = mapCommunitySentimentPayload({ | ||
| communitySentiment: data?.extra?.community_sentiment, | ||
| discussions: data?.extra?.discussions, | ||
| }); | ||
| if (communitySentiment) { | ||
| fixedData.communitySentiment = communitySentiment; | ||
| } |
There was a problem hiding this comment.
add this in separate try catch, schema throw could then break the whole processing, better that it fails separate but keeps post update working as is?
There was a problem hiding this comment.
Done in aa1c509 — mapping now goes through a shared tryMapCommunitySentimentPayload (try/catch + warn log) in both article and collection processors, so a bad take is skipped while the rest of the post update applies. Worker tests updated to assert exactly that.
| provider: z.string(), | ||
| url: z.string(), | ||
| points: z.number(), | ||
| comments_count: z.number(), |
There was a problem hiding this comment.
i think this should all be optional, go side has omitempty which means this could be undefined?
- drop the Post.communitySentiment field resolver (GraphORM jsonType mapping already hydrates the column) - migration: ADD COLUMN IF NOT EXISTS / DROP COLUMN IF EXISTS - isolate sentiment mapping in a shared try/catch wrapper so a bad take logs and skips instead of failing the whole post update - discussions element fields nullish (Go omitempty defensiveness); entries missing provider/url are dropped, counts default 0 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ly-api into feat/community-sentiment
Stores and exposes the community take (community sentiment) produced by yggdrasil's community-takes pipeline.
communitySentimentjsonb on Post (all STI types), migration1784035940370-AddCommunitySentimentToPostapi.content-publishedupdate path: Zod-validated (leanenum, breakdown sums to 100), tolerant of omitempty-omitted fields, malformed payloads dropped+logged; absent take never touches the columncommunitySentimentobject on Post (+ discussions with per-source points/comment counts), null when absentConsumed by dailydotdev/apps#6295. Produced by dailydotdev/yggdrasil#703.
🤖 Generated with Claude Code