Skip to content

feat: community sentiment on posts - #3999

Merged
rebelchris merged 11 commits into
mainfrom
feat/community-sentiment
Jul 20, 2026
Merged

feat: community sentiment on posts#3999
rebelchris merged 11 commits into
mainfrom
feat/community-sentiment

Conversation

@rebelchris

@rebelchris rebelchris commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Stores and exposes the community take (community sentiment) produced by yggdrasil's community-takes pipeline.

  • communitySentiment jsonb on Post (all STI types), migration 1784035940370-AddCommunitySentimentToPost
  • Ingestion on the existing api.content-published update path: Zod-validated (lean enum, breakdown sums to 100), tolerant of omitempty-omitted fields, malformed payloads dropped+logged; absent take never touches the column
  • GraphQL: typed communitySentiment object on Post (+ discussions with per-source points/comment counts), null when absent
  • Tests: worker create/update paths, mapper edge cases, GraphQL shape

Consumed by dailydotdev/apps#6295. Produced by dailydotdev/yggdrasil#703.

🤖 Generated with Claude Code

rebelchris and others added 6 commits July 14, 2026 16:26
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>
@pulumi

pulumi Bot commented Jul 15, 2026

Copy link
Copy Markdown

🍹 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

@rebelchris
rebelchris requested a review from capJavert July 16, 2026 11:02
Comment thread src/schema/posts.ts Outdated
mapCloudinaryUrl(post.contentHtml),
creatorTwitterImage: (post: GQLPost): GQLPost['creatorTwitterImage'] =>
mapCloudinaryUrl(post.creatorTwitterImage),
communitySentiment: (post: GQLPost): GQLPost['communitySentiment'] =>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not needed, we have graphorm already

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if not exists

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also below

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in aa1c509ADD COLUMN IF NOT EXISTS / DROP COLUMN IF EXISTS.

Comment thread src/entity/posts/Post.ts
@Column({ nullable: true })
readTime?: number;

@Column({ type: 'jsonb', nullable: true })

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if not specific reason, better jsonb is empty object by default, easier to update as flags then

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +40 to +46
const communitySentiment = mapCommunitySentimentPayload({
communitySentiment: data?.extra?.community_sentiment,
discussions: data?.extra?.discussions,
});
if (communitySentiment) {
fixedData.communitySentiment = communitySentiment;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/common/schema/communitySentiment.ts Outdated
provider: z.string(),
url: z.string(),
points: z.number(),
comments_count: z.number(),

@capJavert capJavert Jul 17, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this should all be optional, go side has omitempty which means this could be undefined?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in aa1c509 — the discussion element fields are now nullish (entries missing provider/url get dropped, counts default to 0). The take-level arrays/optional strings were already made nullish earlier for the same Go-omitempty reason (b9b8473).

rebelchris and others added 3 commits July 17, 2026 16:16
- 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>
@rebelchris
rebelchris merged commit 05bae32 into main Jul 20, 2026
20 checks passed
@rebelchris
rebelchris deleted the feat/community-sentiment branch July 20, 2026 09:06
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