Created
July 5, 2023 20:43
-
-
Save ciscoheat/9248587abbc11ca885a35684130b8b2f to your computer and use it in GitHub Desktop.
Rate limiter for SvelteKit
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { RateLimiter } from 'sveltekit-rate-limiter/server'; | |
import { superValidate } from '$lib/server'; | |
import { setFlash } from 'sveltekit-flash-message/server'; | |
import { fail } from '@sveltejs/kit'; | |
import type { Actions } from '@sveltejs/kit'; | |
import { schema as contactSchema } from './schema.js'; | |
const limiter = new RateLimiter({ | |
rates: { | |
IPUA: [5, 'h'] | |
} | |
}); | |
export const load = async (event) => { | |
const form = await superValidate(event, contactSchema); | |
return { form }; | |
}; | |
export const actions: Actions = { | |
contact: async (event) => { | |
const form = await superValidate(event, contactSchema); | |
if (await limiter.isLimited(event)) { | |
setFlash( | |
{ | |
type: 'error', | |
message: 'You Have Been Rate Limited, Please Try Later' | |
}, | |
event | |
); | |
return fail(429, { form }); | |
} | |
if (!form.valid) { | |
return fail(400, { form }); | |
} | |
try { | |
console.log( | |
'Sending email', | |
form.data.name, | |
form.data.email, | |
form.data.subject, | |
form.data.message | |
); | |
const response = new Response(null, { status: 502 }); | |
if (response.status !== 200) { | |
setFlash( | |
{ | |
type: 'error', | |
message: 'Email Request Failed, Please Try Later' | |
}, | |
event | |
); | |
return fail(response.status, { form }); | |
} | |
} catch (err) { | |
setFlash( | |
{ type: 'error', message: 'Error Occurred, Please Try Later' }, | |
event | |
); | |
return fail(500, { form }); | |
} | |
setFlash( | |
{ type: 'success', message: 'Your Email Has Been Sent' }, | |
event | |
); | |
return { form }; | |
} | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script lang="ts"> | |
import { superForm } from '$lib/client'; | |
import type { PageData, Snapshot } from './$types'; | |
import SuperDebug from '$lib/client/SuperDebug.svelte'; | |
import { schema as contactSchema } from './schema'; | |
import { initFlash } from 'sveltekit-flash-message/client'; | |
import { page } from '$app/stores'; | |
export let data: PageData; | |
const flash = initFlash(page); | |
const { form, errors, enhance, delayed, tainted, message } = superForm( | |
data.form, | |
{ | |
resetForm: true, | |
taintedMessage: null, | |
validators: contactSchema, | |
stickyNavbar: '#nav' | |
} | |
); | |
let formData = { | |
name: $form.name, | |
email: $form.email, | |
subject: $form.subject, | |
message: $form.message | |
}; | |
export const snapshot: Snapshot = { | |
capture: () => formData, | |
restore: (value) => (formData = value) | |
}; | |
$: if ($flash) { | |
console.log('Toast', $flash.type, $flash.message); | |
} | |
</script> | |
<SuperDebug data={{ $form, $errors, $tainted }} /> | |
{#if $message}<h4>{$message}</h4>{/if} | |
<form | |
class="mt-4 transition-all" | |
method="POST" | |
action="?/contact" | |
use:enhance | |
class:!mt-5={$errors.name} | |
> | |
<div class="form-item relative mb-5 mt-2"> | |
<input | |
id="name" | |
name="name" | |
placeholder="Name" | |
type="text" | |
bind:value={$form.name} | |
class="peer input my-2 block w-full !rounded-md bg-transparent px-4 text-sm text-secondary placeholder-secondary shadow-md outline outline-2 outline-secondary/20 backdrop-blur-sm transition-all focus-visible:outline-[2.5px] focus-visible:outline-neutral" | |
class:!outline-red-500={$errors.name} | |
/> | |
{#if $errors.name} | |
<label | |
class="absolute left-2.5 top-[18px] z-10 px-[10px] text-[12px] font-bold text-secondary transition-all peer-valid:top-[-9px] peer-valid:bg-primary peer-focus-visible:top-[-9px] peer-focus-visible:bg-primary" | |
for="name" | |
> | |
<span class="text-red-500">{$errors?.name}</span> | |
</label> | |
{/if} | |
</div> | |
<div class="form-item relative mb-5 mt-2"> | |
<input | |
id="email" | |
name="email" | |
placeholder="Email" | |
type="text" | |
bind:value={$form.email} | |
class="peer input my-2 block w-full !rounded-md bg-transparent px-4 text-sm text-secondary placeholder-secondary shadow-md outline outline-2 outline-secondary/20 backdrop-blur-sm transition-all focus-visible:outline-[2.5px] focus-visible:outline-neutral" | |
class:!outline-red-500={$errors.email} | |
/> | |
{#if $errors.email} | |
<label | |
class="absolute left-2.5 top-[18px] z-10 px-[10px] text-[12px] font-bold text-secondary transition-all peer-valid:top-[-9px] peer-valid:bg-primary peer-focus-visible:top-[-9px] peer-focus-visible:bg-primary" | |
for="email" | |
> | |
<span class="text-red-500">{$errors.email[0]}</span> | |
</label> | |
{/if} | |
</div> | |
<div class="form-item relative mb-5 mt-2"> | |
<input | |
id="subject" | |
name="subject" | |
placeholder="Subject" | |
type="text" | |
bind:value={$form.subject} | |
class="peer input my-2 block w-full !rounded-md bg-transparent px-4 text-sm text-secondary placeholder-secondary shadow-md outline outline-2 outline-secondary/20 backdrop-blur-sm transition-all focus-visible:outline-[2.5px] focus-visible:outline-neutral" | |
class:!outline-red-500={$errors.subject} | |
/> | |
{#if $errors.subject} | |
<label | |
class="absolute left-2.5 top-[18px] z-10 px-[10px] text-[12px] font-bold text-secondary transition-all peer-valid:top-[-9px] peer-valid:bg-primary peer-focus-visible:top-[-9px] peer-focus-visible:bg-primary" | |
for="subject" | |
> | |
<span class="text-red-500">{$errors.subject}</span> | |
</label> | |
{/if} | |
</div> | |
<div class="form-item relative mb-5 mt-2"> | |
<textarea | |
id="message" | |
name="message" | |
placeholder="Message" | |
bind:value={$form.message} | |
class="peer textarea my-2 block w-full resize-none !rounded-md bg-transparent px-4 pt-3 text-sm text-secondary placeholder-secondary shadow-md outline outline-2 outline-secondary/20 backdrop-blur-sm transition-all focus-visible:outline-[2.5px] focus-visible:outline-neutral" | |
class:!outline-red-500={$errors.message} | |
rows="5" | |
/> | |
{#if $errors.message} | |
<label | |
class="absolute left-2.5 top-[18px] z-10 px-[10px] text-[12px] font-bold text-secondary transition-all peer-valid:top-[-9px] peer-valid:bg-primary peer-focus-visible:top-[-9px] peer-focus-visible:bg-primary" | |
for="message" | |
> | |
<span class="text-red-500">{$errors.message}</span> | |
</label> | |
{/if} | |
</div> | |
<button>Send</button> | |
</form> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { z } from 'zod'; | |
export const schema = z.object({ | |
name: z.string().min(1), | |
email: z.string().email(), | |
subject: z.string().min(1), | |
message: z.string().min(1) | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment