From 45e559a27b4081d12bde7e7106fda7573704bcf4 Mon Sep 17 00:00:00 2001 From: capJavert Date: Mon, 25 May 2026 12:17:01 +0200 Subject: [PATCH 1/2] feat: expose post highlight --- __tests__/highlights.ts | 4 ++++ __tests__/posts.ts | 44 ++++++++++++++++++++++++++++++++++++++++ src/graphorm/index.ts | 43 +++++++++++++++++++++++++++++++++++++++ src/remoteConfig.ts | 1 + src/schema/highlights.ts | 1 + src/schema/posts.ts | 7 +++++++ 6 files changed, 100 insertions(+) diff --git a/__tests__/highlights.ts b/__tests__/highlights.ts index 336e461644..8916eb35b1 100644 --- a/__tests__/highlights.ts +++ b/__tests__/highlights.ts @@ -127,6 +127,7 @@ const MAJOR_HEADLINES_QUERY = ` channel highlightedAt headline + significance post { id title @@ -390,16 +391,19 @@ describe('query majorHeadlines', () => { expect.objectContaining({ channel: 'vibes', headline: 'Newer breaking headline', + significance: 'breaking', post: { id: 'h1', title: 'Test Post 1' }, }), expect.objectContaining({ channel: 'vibes', headline: 'Breaking headline', + significance: 'breaking', post: { id: 'h2', title: 'Test Post 2' }, }), expect.objectContaining({ channel: 'agentic', headline: 'Major headline', + significance: 'major', post: { id: 'h4', title: 'Test Post 4' }, }), ]); diff --git a/__tests__/posts.ts b/__tests__/posts.ts index 42f2c99f1e..f4d954c0d1 100644 --- a/__tests__/posts.ts +++ b/__tests__/posts.ts @@ -47,6 +47,10 @@ import { WelcomePost, YouTubePost, } from '../src/entity'; +import { + PostHighlight, + PostHighlightSignificance, +} from '../src/entity/PostHighlight'; import { Roles, SourceMemberRoles, sourceRoleRank } from '../src/roles'; import { sourcesFixture } from './fixture/source'; import { @@ -941,6 +945,46 @@ describe('sharedPost field', () => { }); }); +describe('postHighlight field', () => { + const QUERY = `{ + post(id: "p1") { + postHighlight { + id + channel + headline + significance + highlightedAt + } + } + }`; + + it('returns null when the post has no active highlight', async () => { + const res = await client.query(QUERY); + expect(res.errors).toBeFalsy(); + expect(res.data.post.postHighlight).toBeNull(); + }); + + it('returns the active highlight when one exists', async () => { + await con.getRepository(PostHighlight).save({ + postId: 'p1', + channel: 'vibes', + highlightedAt: new Date(), + headline: 'Breaking headline', + significance: PostHighlightSignificance.Breaking, + }); + + const res = await client.query(QUERY); + expect(res.errors).toBeFalsy(); + expect(res.data.post.postHighlight).toEqual({ + id: expect.any(String), + channel: 'vibes', + headline: 'Breaking headline', + significance: 'breaking', + highlightedAt: expect.any(String), + }); + }); +}); + describe('type field', () => { const QUERY = `{ post(id: "p1") { diff --git a/src/graphorm/index.ts b/src/graphorm/index.ts index c92941698a..08992772d1 100644 --- a/src/graphorm/index.ts +++ b/src/graphorm/index.ts @@ -40,6 +40,7 @@ import { domainOnly, getSmartTitle, getTranslationRecord, + ONE_HOUR_IN_SECONDS, transformDate, } from '../common'; import { GQLComment } from '../schema/comments'; @@ -54,6 +55,10 @@ import { import { whereVordrFilter } from '../common/vordr'; import { MIN_INDEXABLE_REPUTATION } from '../common/users'; import { UserCompany, Post } from '../entity'; +import { + PostHighlightSignificance, + toPostHighlightSignificanceLabel, +} from '../entity/PostHighlight'; import { ContentPreferenceStatus, ContentPreferenceType, @@ -93,6 +98,15 @@ export enum LocationVerificationStatus { Verified = 'verified', } +const getPostHighlightTtlSeconds = (): number => { + const DEFAULT_POST_HIGHLIGHT_TTL_SECONDS = 12 * ONE_HOUR_IN_SECONDS; + + return ( + remoteConfig.vars.postHighlightTtlSeconds ?? + DEFAULT_POST_HIGHLIGHT_TTL_SECONDS + ); +}; + const existsByUserAndPost = (entity: string, build?: (queryBuilder: QueryBuilder) => QueryBuilder) => (ctx: Context, alias: string, qb: QueryBuilder): string => { @@ -726,6 +740,24 @@ const obj = new GraphORM({ .andWhere(`${childAlias}."deleted" = false`), }, }, + postHighlight: { + relation: { + isMany: false, + customRelation: (_, parentAlias, childAlias, qb): QueryBuilder => + qb + .where(`${childAlias}."postId" = ${parentAlias}."id"`) + .andWhere(`${childAlias}."significance" != :unspecified`, { + unspecified: PostHighlightSignificance.Unspecified, + }) + .andWhere(`${childAlias}."retiredAt" IS NULL`) + .andWhere( + `${childAlias}."highlightedAt" > now() - (:ttlSeconds || ' seconds')::interval`, + { ttlSeconds: getPostHighlightTtlSeconds() }, + ) + .orderBy(`${childAlias}."significance"`, 'ASC') + .addOrderBy(`${childAlias}."highlightedAt"`, 'DESC'), + }, + }, liveRoom: { relation: { isMany: false, @@ -2739,6 +2771,17 @@ const obj = new GraphORM({ updatedAt: { transform: transformDate }, }, }, + PostHighlight: { + fields: { + significance: { + transform: (value: PostHighlightSignificance) => + toPostHighlightSignificanceLabel(value), + }, + highlightedAt: { transform: transformDate }, + createdAt: { transform: transformDate }, + updatedAt: { transform: transformDate }, + }, + }, }); export default obj; diff --git a/src/remoteConfig.ts b/src/remoteConfig.ts index 6a6e4052fd..1a18217044 100644 --- a/src/remoteConfig.ts +++ b/src/remoteConfig.ts @@ -53,6 +53,7 @@ export type RemoteConfigValue = { newViewLogs: boolean; verboseGqlLogging: boolean; engagementAdsEnabled: boolean; + postHighlightTtlSeconds: number; }; class RemoteConfig { diff --git a/src/schema/highlights.ts b/src/schema/highlights.ts index a27c62c92c..cc1fc51665 100644 --- a/src/schema/highlights.ts +++ b/src/schema/highlights.ts @@ -57,6 +57,7 @@ export const typeDefs = /* GraphQL */ ` channel: String! highlightedAt: DateTime! headline: String! + significance: String createdAt: DateTime! updatedAt: DateTime! } diff --git a/src/schema/posts.ts b/src/schema/posts.ts index 7329694ab5..6f933c887e 100644 --- a/src/schema/posts.ts +++ b/src/schema/posts.ts @@ -775,6 +775,13 @@ export const typeDefs = /* GraphQL */ ` """ sharedPost: Post + """ + Currently-active highlight for this post, across all significance tiers + (breaking, major, notable, routine). Null when no highlight is active or + it has expired. + """ + postHighlight: PostHighlight + """ Additional information required for analytics purposes """ From e23caad8e662b1284751a1aaa913659a0a2dc6e1 Mon Sep 17 00:00:00 2001 From: capJavert Date: Mon, 25 May 2026 14:58:40 +0200 Subject: [PATCH 2/2] feat: pr feedback --- __tests__/posts.ts | 43 +++++++++++++++++++++++++++++++++++++++++++ src/graphorm/index.ts | 3 ++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/__tests__/posts.ts b/__tests__/posts.ts index f4d954c0d1..dce22e7a02 100644 --- a/__tests__/posts.ts +++ b/__tests__/posts.ts @@ -983,6 +983,49 @@ describe('postHighlight field', () => { highlightedAt: expect.any(String), }); }); + + it('returns null when the highlight significance is unspecified', async () => { + await con.getRepository(PostHighlight).save({ + postId: 'p1', + channel: 'vibes', + highlightedAt: new Date(), + headline: 'Unspecified headline', + significance: PostHighlightSignificance.Unspecified, + }); + + const res = await client.query(QUERY); + expect(res.errors).toBeFalsy(); + expect(res.data.post.postHighlight).toBeNull(); + }); + + it('returns null when the highlight is retired', async () => { + await con.getRepository(PostHighlight).save({ + postId: 'p1', + channel: 'vibes', + highlightedAt: new Date(), + headline: 'Retired headline', + significance: PostHighlightSignificance.Breaking, + retiredAt: new Date(), + }); + + const res = await client.query(QUERY); + expect(res.errors).toBeFalsy(); + expect(res.data.post.postHighlight).toBeNull(); + }); + + it('returns null when the highlight is older than the TTL', async () => { + await con.getRepository(PostHighlight).save({ + postId: 'p1', + channel: 'vibes', + highlightedAt: new Date(Date.now() - 24 * 60 * 60 * 1000), + headline: 'Stale headline', + significance: PostHighlightSignificance.Breaking, + }); + + const res = await client.query(QUERY); + expect(res.errors).toBeFalsy(); + expect(res.data.post.postHighlight).toBeNull(); + }); }); describe('type field', () => { diff --git a/src/graphorm/index.ts b/src/graphorm/index.ts index 08992772d1..661307df14 100644 --- a/src/graphorm/index.ts +++ b/src/graphorm/index.ts @@ -755,7 +755,8 @@ const obj = new GraphORM({ { ttlSeconds: getPostHighlightTtlSeconds() }, ) .orderBy(`${childAlias}."significance"`, 'ASC') - .addOrderBy(`${childAlias}."highlightedAt"`, 'DESC'), + .addOrderBy(`${childAlias}."highlightedAt"`, 'DESC') + .limit(1), }, }, liveRoom: {