Skip to content

Commit d98d498

Browse files
davidercruzclaude
andauthored
feat: onboarding personas query (#3834)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9e28890 commit d98d498

5 files changed

Lines changed: 175 additions & 0 deletions

File tree

__tests__/feeds.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
import { PollOption } from '../src/entity/polls/PollOption';
3434
import { SourceMemberRoles } from '../src/roles';
3535
import { Category } from '../src/entity/Category';
36+
import { Persona } from '../src/entity/Persona';
3637
import { FastifyInstance } from 'fastify';
3738
import {
3839
feedFields,
@@ -2809,6 +2810,69 @@ describe('query tagsCategories', () => {
28092810
});
28102811
});
28112812

2813+
describe('query onboardingPersonas', () => {
2814+
const QUERY = `{
2815+
onboardingPersonas {
2816+
id
2817+
title
2818+
emoji
2819+
tags
2820+
}
2821+
}`;
2822+
2823+
it('should return personas ordered by sortOrder', async () => {
2824+
await saveFixtures(con, Persona, [
2825+
{
2826+
id: 'beta',
2827+
title: 'Beta Persona',
2828+
emoji: '🅱️',
2829+
tags: ['javascript', 'react'],
2830+
sortOrder: 2,
2831+
},
2832+
{
2833+
id: 'alpha',
2834+
title: 'Alpha Persona',
2835+
emoji: '🅰️',
2836+
tags: ['python', 'golang'],
2837+
sortOrder: 1,
2838+
},
2839+
{
2840+
id: 'gamma',
2841+
title: 'Gamma Persona',
2842+
emoji: '🅶',
2843+
tags: ['rust'],
2844+
sortOrder: 3,
2845+
},
2846+
]);
2847+
2848+
const res = await client.query(QUERY);
2849+
2850+
expect(res.errors).toBeFalsy();
2851+
expect(res.data).toEqual({
2852+
onboardingPersonas: [
2853+
{
2854+
id: 'alpha',
2855+
title: 'Alpha Persona',
2856+
emoji: '🅰️',
2857+
tags: ['python', 'golang'],
2858+
},
2859+
{
2860+
id: 'beta',
2861+
title: 'Beta Persona',
2862+
emoji: '🅱️',
2863+
tags: ['javascript', 'react'],
2864+
},
2865+
{
2866+
id: 'gamma',
2867+
title: 'Gamma Persona',
2868+
emoji: '🅶',
2869+
tags: ['rust'],
2870+
},
2871+
],
2872+
});
2873+
});
2874+
});
2875+
28122876
describe('query advancedSettings', () => {
28132877
it('should return the list of the advanced settings', async () => {
28142878
const QUERY = `{

src/entity/Persona.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Column, Entity, PrimaryColumn, UpdateDateColumn } from 'typeorm';
2+
3+
@Entity()
4+
export class Persona {
5+
@PrimaryColumn({ type: 'text' })
6+
id: string;
7+
8+
@Column({ type: 'text' })
9+
title: string;
10+
11+
@Column({ type: 'text' })
12+
emoji: string;
13+
14+
@Column({ type: 'text', array: true, default: [] })
15+
tags: string[];
16+
17+
@Column({ type: 'int', default: 0 })
18+
sortOrder: number;
19+
20+
@Column({ default: () => 'now()' })
21+
createdAt: Date;
22+
23+
@UpdateDateColumn()
24+
updatedAt: Date;
25+
}

src/entity/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export * from './TagSegment';
2727
export * from './View';
2828
export * from './Comment';
2929
export * from './Keyword';
30+
export * from './Persona';
3031
export * from './PostKeyword';
3132
export * from './CommentMention';
3233
export * from './ReputationEvent';
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { MigrationInterface, QueryRunner } from 'typeorm';
2+
3+
export class Persona1777477090471 implements MigrationInterface {
4+
name = 'Persona1777477090471';
5+
6+
public async up(queryRunner: QueryRunner): Promise<void> {
7+
await queryRunner.query(/* sql */ `
8+
CREATE TABLE "persona" (
9+
"id" text NOT NULL,
10+
"title" text NOT NULL,
11+
"emoji" text NOT NULL,
12+
"tags" text array NOT NULL DEFAULT '{}',
13+
"sortOrder" integer NOT NULL DEFAULT 0,
14+
"createdAt" TIMESTAMP NOT NULL DEFAULT now(),
15+
"updatedAt" TIMESTAMP NOT NULL DEFAULT now(),
16+
CONSTRAINT "PK_persona_id" PRIMARY KEY ("id")
17+
)
18+
`);
19+
20+
await queryRunner.query(
21+
/* sql */ `
22+
INSERT INTO "persona" ("id", "title", "emoji", "tags", "sortOrder")
23+
VALUES
24+
($1, 'Frontend Developer', '🌐',
25+
ARRAY['webdev','javascript','react','css','typescript','html','nextjs','tailwind-css','vuejs','angular','frontend','ui-design']::text[], 1),
26+
($2, 'Backend Developer', '⚙️',
27+
ARRAY['python','java','golang','database','architecture','microservices','rest-api','graphql','docker','postgresql','redis','kafka']::text[], 2),
28+
($3, 'AI/ML Engineer', '🤖',
29+
ARRAY['ai','machine-learning','python','data-science','deep-learning','nlp','pytorch','genai','llm','computer-vision','neural-networks','ai-agents']::text[], 3),
30+
($4, 'DevOps / Cloud', '☁️',
31+
ARRAY['devops','cloud','aws','docker','kubernetes','cicd','terraform','linux','containers','infrastructure','monitoring','serverless','github-actions']::text[], 4),
32+
($5, 'Mobile Developer', '📱',
33+
ARRAY['mobile','react-native','swift','android','ios','flutter','kotlin','iphone','firebase','app-store']::text[], 5),
34+
($6, 'Security / Cyber', '🔒',
35+
ARRAY['security','cyber','data-privacy','encryption','malware','phishing','ransomware','vulnerability','compliance','appsec','cryptography']::text[], 6),
36+
($7, 'Game Developer', '🎮',
37+
ARRAY['gaming','game-development','unity','unreal-engine','game-design','3d','blender','c++','vr','c#']::text[], 7),
38+
($8, 'Data Engineer', '📊',
39+
ARRAY['data-science','python','data-analysis','data-visualization','big-data','database','data-engineering','machine-learning','algorithms','math','pytorch']::text[], 8),
40+
($9, 'Tech Lead / Startup', '🚀',
41+
ARRAY['startup','tech-news','leadership','architecture','open-source','career','agile','product-management','venture-capital','business','tools']::text[], 9),
42+
($10, 'Systems Programmer', '🦀',
43+
ARRAY['rust','c','c++','linux','golang','performance','hardware','computing','webassembly','algorithms']::text[], 10)
44+
`,
45+
[
46+
'frontend',
47+
'backend',
48+
'ai-ml',
49+
'devops',
50+
'mobile',
51+
'security',
52+
'gamedev',
53+
'data',
54+
'lead',
55+
'systems',
56+
],
57+
);
58+
}
59+
60+
public async down(queryRunner: QueryRunner): Promise<void> {
61+
await queryRunner.query(`DROP TABLE "persona"`);
62+
}
63+
}

src/schema/feeds.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
UserPost,
1414
} from '../entity';
1515
import { Category } from '../entity/Category';
16+
import { Persona } from '../entity/Persona';
1617
import { GraphQLResolveInfo } from 'graphql';
1718

1819
import { IFieldResolver, IResolvers } from '@graphql-tools/utils';
@@ -106,6 +107,13 @@ interface GQLTagsCategory {
106107
tags: string[];
107108
}
108109

110+
interface GQLPersona {
111+
id: string;
112+
emoji: string;
113+
title: string;
114+
tags: string[];
115+
}
116+
109117
type GQLFeed = {
110118
id: string;
111119
userId: string;
@@ -158,6 +166,13 @@ export const typeDefs = /* GraphQL */ `
158166
tags: [String]!
159167
}
160168
169+
type Persona {
170+
id: String!
171+
title: String!
172+
emoji: String!
173+
tags: [String]!
174+
}
175+
161176
enum Ranking {
162177
"""
163178
Rank by a combination of time and views
@@ -874,6 +889,11 @@ export const typeDefs = /* GraphQL */ `
874889
"""
875890
tagsCategories: [TagsCategory]!
876891
892+
"""
893+
Get persona presets for onboarding tag selection
894+
"""
895+
onboardingPersonas: [Persona]! @cacheControl(maxAge: 600)
896+
877897
"""
878898
Get the list of advanced settings
879899
"""
@@ -2178,6 +2198,8 @@ export const resolvers: IResolvers<unknown, BaseContext> = {
21782198
),
21792199
tagsCategories: (_, __, ctx): Promise<GQLTagsCategory[]> =>
21802200
ctx.getRepository(Category).find({ order: { title: 'ASC' } }),
2201+
onboardingPersonas: (_, __, ctx): Promise<GQLPersona[]> =>
2202+
ctx.getRepository(Persona).find({ order: { sortOrder: 'ASC' } }),
21812203
feedList: async (
21822204
source,
21832205
args: ConnectionArguments,

0 commit comments

Comments
 (0)