Skip to content

Instantly share code, notes, and snippets.

@ciscoheat
Created July 5, 2023 20:43
Show Gist options
  • Save ciscoheat/9248587abbc11ca885a35684130b8b2f to your computer and use it in GitHub Desktop.
Save ciscoheat/9248587abbc11ca885a35684130b8b2f to your computer and use it in GitHub Desktop.
Rate limiter for SvelteKit
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 };
}
};
<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>
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