Skip to content

Commit

Permalink
Merge pull request #111 from eavlongs/bugfix/validate-social-url
Browse files Browse the repository at this point in the history
Added validation for socials URL
  • Loading branch information
s1lvax authored Oct 31, 2024
2 parents 4943cdf + 025fe61 commit 4c13f64
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 18 deletions.
9 changes: 6 additions & 3 deletions src/lib/components/MyProfile/SocialsForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { zodClient } from 'sveltekit-superforms/adapters';
import * as Select from '$lib/components/ui/select';
import { socials } from '$lib/constants/socials';
import { socials, SocialsInputType } from '$lib/constants/socials';
export let data: SuperValidated<Infer<SocialsSchema>>;
Expand All @@ -23,6 +23,9 @@
value: $formData.social
}
: undefined;
$: selectedSocialInputType =
socials.find((s) => s.name === $formData.social)?.inputType ?? SocialsInputType.URL;
</script>

<form
Expand Down Expand Up @@ -59,9 +62,9 @@
</Form.Control>
<Form.FieldErrors />
</Form.Field>
<Form.Field {form} name="socialURL">
<Form.Field {form} name="socialURL" >
<Form.Control let:attrs>
<Form.Label>Social URL</Form.Label>
<Form.Label>{selectedSocialInputType}</Form.Label>
<Input {...attrs} bind:value={$formData.socialURL} />
</Form.Control>
<Form.FieldErrors />
Expand Down
35 changes: 25 additions & 10 deletions src/lib/constants/socials.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
//Social media
// Import to add differences in how they're handled in the frontend

//If the user should be redirected to a profile, make redirect true if not make redirect false and it will copy the contents to the clipboard

import {
IconBrandDiscordFilled,
IconBrandLinkedin,
Expand All @@ -11,30 +6,50 @@ import {
IconBrandSpotifyFilled
} from '@tabler/icons-svelte';

//Social media
// Import to add differences in how they're handled in the frontend

// To be adjusted if we ever need more input types for socials
export enum SocialsInputType {
URL = 'Social URL',
USERNAME = 'Social Username'
}

//If the user should be redirected to a profile, make redirect true if not make redirect false and it will copy the contents to the clipboard
export const socials = [
{
name: 'Discord',
icon: IconBrandDiscordFilled,
redirect: false
redirect: false,
inputType: SocialsInputType.USERNAME,
domains: ['discord.com']
},
{
name: 'LinkedIn',
icon: IconBrandLinkedin,
redirect: true
redirect: true,
inputType: SocialsInputType.URL,
domains: ['linkedin.com']
},
{
name: 'Telegram',
icon: IconBrandTelegram,
redirect: false
redirect: false,
inputType: SocialsInputType.USERNAME,
domains: ['t.me']
},
{
name: 'Spotify',
icon: IconBrandSpotifyFilled,
redirect: true
redirect: true,
inputType: SocialsInputType.URL,
domains: ['spotify.com']
},
{
name: 'Gitlab',
icon: IconBrandGitlab,
redirect: true
redirect: true,
inputType: SocialsInputType.URL,
domains: ['gitlab.com']
}
];
64 changes: 60 additions & 4 deletions src/lib/schemas/socials.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,64 @@
import { SocialsInputType } from '$lib/constants/socials';
import { findSocial } from '$lib/utils/handleSocialClick';
import { z } from 'zod';

export const socialsSchema = z.object({
social: z.string().min(1).max(60),
socialURL: z.string().min(1).max(200)
});
export const socialsSchema = z
.object({
social: z.string().min(1).max(60),
socialURL: z.string().min(1).max(200)
})
.superRefine((obj, ctx) => {
const socialObject = findSocial(obj.social);
if (socialObject?.inputType !== SocialsInputType.URL) {
return;
}
if (!obj.socialURL) return;

const URLPattern = new RegExp(
'(https:\\/\\/www\\.|http:\\/\\/www\\.|https:\\/\\/|http:\\/\\/)?[a-zA-Z0-9]{2,}(\\.[a-zA-Z0-9]{2,})(\\.[a-zA-Z0-9]{2,})?'
);

const isUrlValid = URLPattern.test(obj.socialURL);

if (!isUrlValid) {
ctx.addIssue({
code: z.ZodIssueCode.invalid_string,
message: 'Invalid URL',
path: ['socialURL'],
validation: 'url'
});
return;
}

// cleanup the URL by adding https:// if it's not already there
let url = obj.socialURL;

if (!(url.startsWith('http://') || url.startsWith('https://'))) {
url = 'https://' + url;
}

// extract the domain from the URL
const domain = new URL(url).hostname;

if (socialObject.domains.includes(domain)) {
return true;
}

ctx.addIssue({
code: z.ZodIssueCode.invalid_string,
message: 'URL provided is not a valid domain for the selected social',
validation: 'url',
path: ['socialURL']
});
})
.transform((data) => {
if (findSocial(data.social)?.inputType !== SocialsInputType.URL) return data;

// cleanup the URL by adding https:// if it's not already there
if (!(data.socialURL.startsWith('http://') || data.socialURL.startsWith('https://'))) {
data.socialURL = 'https://' + data.socialURL;
}
return data;
});

export type SocialsSchema = typeof socialsSchema;
2 changes: 1 addition & 1 deletion src/lib/utils/handleSocialClick.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { socials as socialMediaList } from '$lib/constants/socials';
import type { Social } from '@prisma/client';
import { copyToClipboard } from './copyToClipboard';

const findSocial = (socialName: string) => {
export const findSocial = (socialName: string) => {
return socialMediaList.find((social) => social.name === socialName);
};

Expand Down

0 comments on commit 4c13f64

Please sign in to comment.