Skip to content

Commit 8fb81dd

Browse files
refactor: use zod to parse socials, fix parsng errors (#3317)
1 parent 6124ff8 commit 8fb81dd

4 files changed

Lines changed: 161 additions & 108 deletions

File tree

__tests__/users.ts

Lines changed: 56 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -59,30 +59,32 @@ import {
5959
} from '../src/entity';
6060
import { sourcesFixture } from './fixture/source';
6161
import {
62-
bskySocialUrlMatch,
6362
CioTransactionalMessageTemplateId,
64-
codepenSocialUrlMatch,
6563
DayOfWeek,
6664
encrypt,
6765
getTimezonedStartOfISOWeek,
6866
ghostUser,
69-
githubSocialUrlMatch,
7067
type GQLUserTopReader,
71-
linkedinSocialUrlMatch,
72-
mastodonSocialUrlMatch,
7368
portfolioLimit,
74-
redditSocialUrlMatch,
75-
roadmapShSocialUrlMatch,
7669
sendEmail,
77-
socialUrlMatch,
78-
stackoverflowSocialUrlMatch,
7970
systemUser,
80-
threadsSocialUrlMatch,
81-
twitterSocialUrlMatch,
8271
updateFlagsStatement,
8372
updateSubscriptionFlags,
8473
UploadPreset,
8574
} from '../src/common';
75+
import {
76+
blueskySchema,
77+
codepenSchema,
78+
githubSchema,
79+
linkedinSchema,
80+
mastodonSchema,
81+
portfolioSchema,
82+
redditSchema,
83+
roadmapSchema,
84+
stackoverflowSchema,
85+
threadsSchema,
86+
twitterSchema,
87+
} from '../src/common/schema/socials';
8688
import { DataSource, In, IsNull } from 'typeorm';
8789
import createOrGetConnection from '../src/db';
8890
import request from 'supertest';
@@ -3863,12 +3865,13 @@ describe('mutation updateUserProfile', () => {
38633865
];
38643866

38653867
valid.forEach((item) => {
3866-
expect(githubSocialUrlMatch.test(item)).toBe(true);
3867-
expect(item.match(githubSocialUrlMatch)?.groups?.value).toBe('lee');
3868+
const result = githubSchema.safeParse(item);
3869+
expect(result.success).toBe(true);
3870+
if (result.success) expect(result.data).toBe('lee');
38683871
});
38693872

38703873
invalid.forEach((item) => {
3871-
expect(githubSocialUrlMatch.test(item)).toBe(false);
3874+
expect(githubSchema.safeParse(item).success).toBe(false);
38723875
});
38733876
});
38743877

@@ -3897,12 +3900,13 @@ describe('mutation updateUserProfile', () => {
38973900
];
38983901

38993902
valid.forEach((item) => {
3900-
expect(twitterSocialUrlMatch.test(item)).toBe(true);
3901-
expect(item.match(twitterSocialUrlMatch)?.groups?.value).toBe('lee');
3903+
const result = twitterSchema.safeParse(item);
3904+
expect(result.success).toBe(true);
3905+
if (result.success) expect(result.data).toBe('lee');
39023906
});
39033907

39043908
invalid.forEach((item) => {
3905-
expect(twitterSocialUrlMatch.test(item)).toBe(false);
3909+
expect(twitterSchema.safeParse(item).success).toBe(false);
39063910
});
39073911
});
39083912

@@ -3925,11 +3929,11 @@ describe('mutation updateUserProfile', () => {
39253929
];
39263930

39273931
valid.forEach((item) => {
3928-
expect(bskySocialUrlMatch.test(item)).toBe(true);
3932+
expect(blueskySchema.safeParse(item).success).toBe(true);
39293933
});
39303934

39313935
invalid.forEach((item) => {
3932-
expect(bskySocialUrlMatch.test(item)).toBe(false);
3936+
expect(blueskySchema.safeParse(item).success).toBe(false);
39333937
});
39343938
});
39353939

@@ -3950,12 +3954,13 @@ describe('mutation updateUserProfile', () => {
39503954
];
39513955

39523956
valid.forEach((item) => {
3953-
expect(roadmapShSocialUrlMatch.test(item)).toBe(true);
3954-
expect(item.match(roadmapShSocialUrlMatch)?.groups?.value).toBe('lee');
3957+
const result = roadmapSchema.safeParse(item);
3958+
expect(result.success).toBe(true);
3959+
if (result.success) expect(result.data).toBe('lee');
39553960
});
39563961

39573962
invalid.forEach((item) => {
3958-
expect(roadmapShSocialUrlMatch.test(item)).toBe(false);
3963+
expect(roadmapSchema.safeParse(item).success).toBe(false);
39593964
});
39603965
});
39613966

@@ -3980,12 +3985,13 @@ describe('mutation updateUserProfile', () => {
39803985
];
39813986

39823987
valid.forEach((item) => {
3983-
expect(threadsSocialUrlMatch.test(item)).toBe(true);
3984-
expect(item.match(threadsSocialUrlMatch)?.groups?.value).toBe('lee');
3988+
const result = threadsSchema.safeParse(item);
3989+
expect(result.success).toBe(true);
3990+
if (result.success) expect(result.data).toBe('lee');
39853991
});
39863992

39873993
invalid.forEach((item) => {
3988-
expect(threadsSocialUrlMatch.test(item)).toBe(false);
3994+
expect(threadsSchema.safeParse(item).success).toBe(false);
39893995
});
39903996
});
39913997

@@ -4006,12 +4012,13 @@ describe('mutation updateUserProfile', () => {
40064012
];
40074013

40084014
valid.forEach((item) => {
4009-
expect(codepenSocialUrlMatch.test(item)).toBe(true);
4010-
expect(item.match(codepenSocialUrlMatch)?.groups?.value).toBe('lee');
4015+
const result = codepenSchema.safeParse(item);
4016+
expect(result.success).toBe(true);
4017+
if (result.success) expect(result.data).toBe('lee');
40114018
});
40124019

40134020
invalid.forEach((item) => {
4014-
expect(codepenSocialUrlMatch.test(item)).toBe(false);
4021+
expect(codepenSchema.safeParse(item).success).toBe(false);
40154022
});
40164023
});
40174024

@@ -4035,12 +4042,13 @@ describe('mutation updateUserProfile', () => {
40354042
];
40364043

40374044
valid.forEach((item) => {
4038-
expect(redditSocialUrlMatch.test(item)).toBe(true);
4039-
expect(item.match(redditSocialUrlMatch)?.groups?.value).toBe('lee');
4045+
const result = redditSchema.safeParse(item);
4046+
expect(result.success).toBe(true);
4047+
if (result.success) expect(result.data).toBe('lee');
40404048
});
40414049

40424050
invalid.forEach((item) => {
4043-
expect(redditSocialUrlMatch.test(item)).toBe(false);
4051+
expect(redditSchema.safeParse(item).success).toBe(false);
40444052
});
40454053
});
40464054

@@ -4049,9 +4057,9 @@ describe('mutation updateUserProfile', () => {
40494057
'stackoverflow.com/users/999999/lee',
40504058
'https://stackoverflow.com/users/999999/lee',
40514059
'https://stackoverflow.com/users/999999/lee/',
4060+
'999999/lee',
40524061
];
40534062
const invalid = [
4054-
'99999/lee',
40554063
'lee',
40564064
'lee#',
40574065
'http://stackoverflow.com/lee',
@@ -4064,14 +4072,13 @@ describe('mutation updateUserProfile', () => {
40644072
];
40654073

40664074
valid.forEach((item) => {
4067-
expect(stackoverflowSocialUrlMatch.test(item)).toBe(true);
4068-
expect(item.match(stackoverflowSocialUrlMatch)?.groups?.value).toBe(
4069-
'999999/lee',
4070-
);
4075+
const result = stackoverflowSchema.safeParse(item);
4076+
expect(result.success).toBe(true);
4077+
if (result.success) expect(result.data).toBe('999999/lee');
40714078
});
40724079

40734080
invalid.forEach((item) => {
4074-
expect(stackoverflowSocialUrlMatch.test(item)).toBe(false);
4081+
expect(stackoverflowSchema.safeParse(item).success).toBe(false);
40754082
});
40764083
});
40774084

@@ -4092,12 +4099,13 @@ describe('mutation updateUserProfile', () => {
40924099
];
40934100

40944101
valid.forEach((item) => {
4095-
expect(linkedinSocialUrlMatch.test(item)).toBe(true);
4096-
expect(item.match(linkedinSocialUrlMatch)?.groups?.value).toBe('lee');
4102+
const result = linkedinSchema.safeParse(item);
4103+
expect(result.success).toBe(true);
4104+
if (result.success) expect(result.data).toBe('lee');
40974105
});
40984106

40994107
invalid.forEach((item) => {
4100-
expect(linkedinSocialUrlMatch.test(item)).toBe(false);
4108+
expect(linkedinSchema.safeParse(item).success).toBe(false);
41014109
});
41024110
});
41034111

@@ -4117,12 +4125,13 @@ describe('mutation updateUserProfile', () => {
41174125
];
41184126

41194127
valid.forEach((item) => {
4120-
expect(mastodonSocialUrlMatch.test(item)).toBe(true);
4121-
expect(item.match(mastodonSocialUrlMatch)?.groups?.value).toBe(item);
4128+
const result = mastodonSchema.safeParse(item);
4129+
expect(result.success).toBe(true);
4130+
if (result.success) expect(result.data).toBe(item);
41224131
});
41234132

41244133
invalid.forEach((item) => {
4125-
expect(mastodonSocialUrlMatch.test(item)).toBe(false);
4134+
expect(mastodonSchema.safeParse(item).success).toBe(false);
41264135
});
41274136
});
41284137

@@ -4149,12 +4158,13 @@ describe('mutation updateUserProfile', () => {
41494158
];
41504159

41514160
valid.forEach((item) => {
4152-
expect(socialUrlMatch.test(item)).toBe(true);
4153-
expect(item.match(socialUrlMatch)?.groups?.value).toBe(item);
4161+
const result = portfolioSchema.safeParse(item);
4162+
expect(result.success).toBe(true);
4163+
if (result.success) expect(result.data).toBe(item);
41544164
});
41554165

41564166
invalid.forEach((item) => {
4157-
expect(socialUrlMatch.test(item)).toBe(false);
4167+
expect(portfolioSchema.safeParse(item).success).toBe(false);
41584168
});
41594169
});
41604170

src/common/schema/socials.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { z } from 'zod';
2+
3+
const socialUrlSchema = (regex: RegExp) =>
4+
z.preprocess(
5+
(val) => (val === '' ? null : val),
6+
z
7+
.string()
8+
.regex(regex)
9+
.transform((val) => {
10+
const match = val.match(regex);
11+
return match?.groups?.value ?? val;
12+
})
13+
.nullish(),
14+
);
15+
16+
export const roadmapSchema = socialUrlSchema(
17+
/^(?:(?:https:\/\/)?(?:www\.)?roadmap\.sh\/u\/)?(?<value>[\w-]{2,})\/?$/,
18+
);
19+
20+
export const twitterSchema = socialUrlSchema(
21+
/^(?:(?:https:\/\/)?(?:www\.)?(?:twitter|x)\.com\/)?@?(?<value>[\w-]{2,})\/?$/,
22+
);
23+
24+
export const githubSchema = socialUrlSchema(
25+
/^(?:(?:https:\/\/)?(?:www\.)?github\.com\/)?@?(?<value>[\w-]{2,})\/?$/,
26+
);
27+
28+
export const threadsSchema = socialUrlSchema(
29+
/^(?:(?:https:\/\/)?(?:www\.)?threads\.net\/)?@?(?<value>[\w-]{2,})\/?$/,
30+
);
31+
32+
export const codepenSchema = socialUrlSchema(
33+
/^(?:(?:https:\/\/)?(?:www\.)?codepen\.io\/)?(?<value>[\w-]{2,})\/?$/,
34+
);
35+
36+
export const redditSchema = socialUrlSchema(
37+
/^(?:(?:https:\/\/)?(?:www\.)?reddit\.com\/(?:u|user)\/)?(?<value>[\w-]{2,})\/?$/,
38+
);
39+
40+
export const stackoverflowSchema = socialUrlSchema(
41+
/^(?:(?:https:\/\/)?(?:www\.)?stackoverflow\.com\/users\/)?(?<value>\d+\/[\w-]+)\/?$/,
42+
);
43+
44+
export const youtubeSchema = socialUrlSchema(
45+
/^(?:(?:https:\/\/)?(?:www\.)?youtube\.com\/)?@?(?<value>[\w-]{2,})\/?$/,
46+
);
47+
48+
export const linkedinSchema = socialUrlSchema(
49+
/^(?:(?:https:\/\/)?(?:www\.)?linkedin\.com\/in\/)?(?<value>[\w-]{2,})\/?$/,
50+
);
51+
52+
export const mastodonSchema = socialUrlSchema(
53+
/^(?<value>https:\/\/(?:[a-z0-9-]+\.)*[a-z0-9-]+\.[a-z]{2,}\/@[\w-]{2,}\/?)$/,
54+
);
55+
56+
export const hashnodeSchema = socialUrlSchema(
57+
/^(?<value>https:\/\/(?:[a-z0-9-]{1,50}\.){0,5}[a-z0-9-]{1,50}\.[a-z]{2,24}\b([-a-zA-Z0-9@:%_+.~#?&\/=]*))$/,
58+
);
59+
60+
export const portfolioSchema = socialUrlSchema(
61+
/^(?<value>https:\/\/(?:[a-z0-9-]{1,50}\.){0,5}[a-z0-9-]{1,50}\.[a-z]{2,24}\b([-a-zA-Z0-9@:%_+.~#?&\/=]*))$/,
62+
);
63+
64+
export const blueskySchema = socialUrlSchema(
65+
/^(?:(?:https:\/\/)?(?:www\.)?bsky\.app\/profile\/)?(?<value>[\w.-]+)(?:\/.*)?$/,
66+
);
67+
68+
export const socialFieldsSchema = z.object({
69+
github: githubSchema,
70+
twitter: twitterSchema,
71+
threads: threadsSchema,
72+
codepen: codepenSchema,
73+
reddit: redditSchema,
74+
stackoverflow: stackoverflowSchema,
75+
youtube: youtubeSchema,
76+
linkedin: linkedinSchema,
77+
mastodon: mastodonSchema,
78+
roadmap: roadmapSchema,
79+
bluesky: blueskySchema,
80+
hashnode: hashnodeSchema,
81+
portfolio: portfolioSchema,
82+
});
83+
84+
export type SocialFields = z.infer<typeof socialFieldsSchema>;

src/common/users.ts

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -600,42 +600,6 @@ export const shouldAllowRestore = async (
600600
});
601601
};
602602

603-
export const roadmapShSocialUrlMatch =
604-
/^(?:(?:https:\/\/)?(?:www\.)?roadmap\.sh\/u\/)?(?<value>[\w-]{2,})\/?$/;
605-
606-
export const twitterSocialUrlMatch =
607-
/^(?:(?:https:\/\/)?(?:www\.)?(?:twitter|x)\.com\/)?@?(?<value>[\w-]{2,})\/?$/;
608-
609-
export const githubSocialUrlMatch =
610-
/^(?:(?:https:\/\/)?(?:www\.)?github\.com\/)?@?(?<value>[\w-]{2,})\/?$/;
611-
612-
export const threadsSocialUrlMatch =
613-
/^(?:(?:https:\/\/)?(?:www\.)?threads\.net\/)?@?(?<value>[\w-]{2,})\/?$/;
614-
615-
export const codepenSocialUrlMatch =
616-
/^(?:(?:https:\/\/)?(?:www\.)?codepen\.io\/)?(?<value>[\w-]{2,})\/?$/;
617-
618-
export const redditSocialUrlMatch =
619-
/^(?:(?:https:\/\/)?(?:www\.)?reddit\.com\/(?:u|user)\/)?(?<value>[\w-]{2,})\/?$/;
620-
621-
export const stackoverflowSocialUrlMatch =
622-
/^(?:https:\/\/)?(?:www\.)?stackoverflow\.com\/users\/(?<value>\d{2,}\/?[\w-]{2,}?)\/?$/;
623-
624-
export const youtubeSocialUrlMatch =
625-
/^(?:(?:https:\/\/)?(?:www\.)?youtube\.com\/)?@?(?<value>[\w-]{2,})\/?$/;
626-
627-
export const linkedinSocialUrlMatch =
628-
/^(?:(?:https:\/\/)?(?:www\.)?linkedin\.com\/in\/)?(?<value>[\w-]{2,})\/?$/;
629-
630-
export const mastodonSocialUrlMatch =
631-
/^(?<value>https:\/\/(?:[a-z0-9-]+\.)*[a-z0-9-]+\.[a-z]{2,}\/@[\w-]{2,}\/?)$/;
632-
633-
export const socialUrlMatch =
634-
/^(?<value>https:\/\/(?:[a-z0-9-]{1,50}\.){0,5}[a-z0-9-]{1,50}\.[a-z]{2,24}\b([-a-zA-Z0-9@:%_+.~#?&\/=]*))$/;
635-
636-
export const bskySocialUrlMatch =
637-
/^(?:(?:https:\/\/)?(?:www\.)?bsky\.app\/profile\/)?(?<value>[\w.-]+)(?:\/.*)?$/;
638-
639603
export const portfolioLimit = 500;
640604

641605
const MIN_WORK_EXPERIENCE = 1; // Minimum number of work experiences required for profile completion

0 commit comments

Comments
 (0)