<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
	<channel>
		<atom:link href="https://seanbehan.ca/posts/tag/serverless/rss.xml" rel="self" type="application/rss+xml" />
		<title>Sean Behan — serverless</title>
		<link>https://seanbehan.ca/posts/tag/serverless</link>
		<description>Posts tagged “serverless”.</description>
		<language>en-CA</language>
		<!-- RSS wants an address here and readers show the name beside it. -->
		<managingEditor>sean@seanbehan.ca (Sean Behan)</managingEditor>
		<webMaster>sean@seanbehan.ca (Sean Behan)</webMaster>
		<lastBuildDate>Sun, 13 Sep 2026 02:48:49 GMT</lastBuildDate>
		<item>
		<guid isPermaLink="true">https://seanbehan.ca/posts/email-bot</guid>
		<title><![CDATA[Email bot]]></title>
		<description><![CDATA[Cloudflare email routing plus Workers AI: an inbox that answers its own mail.]]></description>
		<link>https://seanbehan.ca/posts/email-bot</link>
		<pubDate>Wed, 04 Dec 2024 20:29:41 GMT</pubDate>
		<category>Cloudflare</category><category>serverless</category><category>TypeScript</category>
		<content:encoded><![CDATA[<h3>How it Works</h3><p>In this post I&#x27;m just going to show you something cool I made using cloudflare workers.</p><pre><code class="language-typescript">import { EmailMessage } from 'cloudflare:email';
import { createMimeMessage } from 'mimetext';

export default {
	async email(message, env, ctx) {
		const content = await new Response(message.raw).text();
		const messages = [
			{ role: 'system', content: 'you are an email responder, reply to the given message' },
			{ role: 'user', content }
		];
		// @ts-expect-error broken bindings
		const { response } = await env.AI.run('@cf/meta/llama-3.2-11b-vision-instruct', { messages });
		const msg = createMimeMessage();
		msg.setHeader('In-Reply-To', message.headers.get('Message-ID') as string);
		msg.setSender({ name: 'Sean Behan', addr: 'contact@seanbehan.ca' });
		msg.setRecipient(message.from);
		msg.setSubject('RE');
		msg.addMessage({
			contentType: 'text/plain',
			data: `${response}\n\nThis was an automated message. If this is urgent, message Sean on Telegram https://t.me/codebam`
		});

		const replyMessage = new EmailMessage('contact@seanbehan.ca', message.from, msg.asRaw());

		await message.reply(replyMessage);
		await message.forward('codebam@riseup.net');
	}
} satisfies ExportedHandler&lt;Env&gt;;</code></pre><p>This small amount of code is all it takes to automatically reply to all my emails with llama3.2!</p><p>Feel free to copy it and use it yourself. The source code is on <a href="https://github.com/codebam/email-bot">GitHub</a>.</p>]]></content:encoded>
	</item>
<item>
		<guid isPermaLink="true">https://seanbehan.ca/posts/svelte-search</guid>
		<title><![CDATA[Search bar in Sveltekit]]></title>
		<description><![CDATA[The fuzzy search on this site, built with fuse.js.]]></description>
		<link>https://seanbehan.ca/posts/svelte-search</link>
		<pubDate>Wed, 06 Dec 2023 20:29:41 GMT</pubDate>
		<category>Cloudflare</category><category>JavaScript</category><category>serverless</category><category>Svelte</category><category>SvelteKit</category><category>TypeScript</category>
		<content:encoded><![CDATA[<h3>Introduction</h3><p>In this post I&#x27;m going to show you how I made the search bar on the posts page and main page of my website.</p><h3>Implementing the Search Bar</h3><p>I used a library called <code>fuse.js</code> for fuzzy searching for titles, here&#x27;s how it works.</p><pre><code class="language-typescript">&lt;script lang="ts"&gt;
	import { Heading } from 'flowbite-svelte';
	import Post from '$lib/components/Post.svelte';
	import Fuse from 'fuse.js';
	import { afterUpdate } from 'svelte';
	export let posts: { path: string; meta: { title: string; date: string } }[];
	let query = '*';
	let results = posts;
	const options = { keys: ['meta.title'] };
	const fuse = new Fuse(posts, options);
	afterUpdate(() =&gt; {
		let results_ = fuse.search(query);
		results = results_.map((result) =&gt; ({ path: result.item.path, meta: result.item.meta }));
	});
&lt;/script&gt;

&lt;Heading
	tag="h4"
	style="display: inline"
	class="ml-8 my-4 text-secondary dark:text-dark-secondary w-auto"&gt;Posts&lt;/Heading
&gt;
&lt;input class="text-secondary" bind:value={query} /&gt;
&lt;ul&gt;
	{#each results as post}
		&lt;Post {post} /&gt;
	{/each}
	{#if results.length === 0}
		{#each posts as post}
			&lt;Post {post} /&gt;
		{/each}
	{/if}
&lt;/ul&gt;</code></pre><h3>How it Works</h3><p>You can see I initialize the query to <code>&#x27;*&#x27;</code>. All this really does is sets the search field to <code>*</code> instead of being empty. Then in the input element I bind to the value of query so that when the user types in the field the value of query will change.</p><p>I do some more initialization of <code>fuse.js</code> to search and then I register <code>afterUpdate()</code>. This allows me to search only when the page changes.</p><p>Finally I added some logic to display all the posts if there are no results, like for example when the page loads.</p><p>All this together looks like the search bar you see on <code>/posts</code> and <code>/</code>.</p>]]></content:encoded>
	</item>
<item>
		<guid isPermaLink="true">https://seanbehan.ca/posts/svelte-auth</guid>
		<title><![CDATA[Auth and Databases in Sveltekit]]></title>
		<description><![CDATA[HTTP Basic Auth with a session cookie in SvelteKit, backed by Cloudflare D1.]]></description>
		<link>https://seanbehan.ca/posts/svelte-auth</link>
		<pubDate>Sun, 12 Nov 2023 05:40:33 GMT</pubDate>
		<category>Cloudflare</category><category>JavaScript</category><category>serverless</category><category>Svelte</category><category>SvelteKit</category><category>TypeScript</category>
		<content:encoded><![CDATA[<h3>Introduction</h3><p>Recently I implemented HTTP Basic Auth using a session cookie in SvelteKit on Cloudflare Pages using Cloudflare D1 as a database backend. Here I&#x27;ll show some code snippets so you can do the same.</p><h3>Project Setup</h3><p>First you would want to set up your project according to <a href="https://developers.cloudflare.com/pages/framework-guides/deploy-a-svelte-site">this</a>.</p><p>So you can have a basic project set up.</p><h3>Database Setup</h3><p>Next you&#x27;ll want to add the database to your project. It&#x27;s easy to create using wrangler. Just remember to link it to your project in your cloudflare dash under settings &gt; functions.</p><pre><code class="language-sh">wrangler d1 create your-database-name</code></pre><p>Then you can copy it into your <a href="https://github.com/codebam/svelte-auth/blob/master/wrangler.toml">`wrangler.toml` </a>so your local dev works with it. To run with support for D1 you can use this oneliner.</p><pre><code class="language-sh">npm run build &amp;&amp; wrangler pages dev .svelte-kit/cloudflare</code></pre><p>My final schema looks like this. Yours might be a bit different, but mine allows for adding songs to a playlist, which was the purpose of <a href="https://github.com/codebam/svelte-auth">that project</a>.</p><pre><code class="language-sql">CREATE TABLE IF NOT EXISTS Users (id TEXT PRIMARY KEY, password TEXT);
CREATE TABLE IF NOT EXISTS Playlist (id TEXT PRIMARY KEY, email TEXT, url TEXT, date TEXT);</code></pre><p>You&#x27;ll want to create these tables using wrangler both locally (on your dev server) and remotely.</p><pre><code class="language-sh">wrangler d1 execute svelte-auth --local --file=./schema.sql
wrangler d1 execute svelte-auth --file=./schema.sql</code></pre><p>Now you can add it to your <a href="https://github.com/codebam/svelte-auth/blob/master/src/app.d.ts">`app.d.ts` </a>like this.</p><pre><code class="language-typescript">declare global {
	namespace App {
		interface Platform {
			env?: {
				DB: D1Database;
			};
			context: {
				waitUntil(promise: Promise&lt;any&gt;): void;
			};
			caches: CacheStorage &amp; { default: Cache };
		}
	}
}

export {};</code></pre><h3>Authentication Implementation</h3><p>Now you&#x27;re ready to set up authentication. It&#x27;s probably easier to just show you how I did it. I imported <code>sha256</code> which is just a simple function I wrote to call web crypto to get the sha256sum of a string.</p><p><a href="https://github.com/codebam/svelte-auth/blob/master/src/routes/login/%2Bpage.server.ts">Here </a>is my code.</p><p>I added <a href="https://kit.svelte.dev/docs/form-actions">form actions</a>. These allow you to quickly write code that will work with HTML <code>&lt;form&gt;</code>&#x27;s.</p><pre><code class="language-typescript">import { redirect } from '@sveltejs/kit';
import sha256 from '$lib/sha256';

export const actions = {
	register: async (event) =&gt; {
		const data = await event.request.formData();
		const email = data.get('email');
		const password = await sha256(data.get('password')?.toString() ?? '');
		const { success } = await event.platform?.env?.DB.prepare('INSERT INTO Users VALUES (?, ?)')
			.bind(email, password)
			.all();
		if (success) {
			event.cookies.set('session', JSON.stringify({ email, password }));
			throw redirect(303, '/');
		}
	},
	login: async (event) =&gt; {
		const data = await event.request.formData();
		const email = data.get('email');
		const password = await sha256(data.get('password')?.toString() ?? '');
		const results = await event.platform?.env?.DB.prepare('SELECT password FROM Users WHERE id=?')
			.bind(email)
			.all();
		if (results.results[0].password === password) {
			event.cookies.set('session', JSON.stringify({ email, password }));
			throw redirect(303, '/');
		}
	}
};</code></pre><h3>Checking Authentication Status</h3><p>Now this is enough to save a cookie with the user auth, but to re-auth on a page we need to check if their cookie is valid. So how I do this is I wrote another function called <a href="https://github.com/codebam/svelte-auth/blob/master/src/lib/tryLogin.ts">`tryLogin.ts`</a>.</p><pre><code class="language-typescript">import type { D1Database } from '@cloudflare/workers-types';

const tryLogin = async (session_cookie: string | undefined, DB: D1Database) =&gt; {
	if (session_cookie) {
		const { email, password } = JSON.parse(session_cookie);
		const results = await DB.prepare('SELECT password FROM Users WHERE id=?').bind(email).all();
		if (results.results[0].password === password) {
			return true;
		}
	}
	return false;
};

export default tryLogin;</code></pre><p>Now I can call this inside <a href="https://github.com/codebam/svelte-auth/blob/master/src/routes/%2Bpage.server.ts">`+page.server.ts`</a>.</p><pre><code class="language-typescript">import tryLogin from '$lib/tryLogin';
import getCurrentSong from '$lib/getCurrentSong';

export const load = async (event) =&gt; ({
	auth: await tryLogin(event.cookies.get('session'), event.platform?.env?.DB),
	song: await getCurrentSong(event.platform?.env?.DB)
});</code></pre><p>Using it inside the <a href="https://github.com/codebam/svelte-auth/blob/master/src/routes/%2Bpage.svelte">`+page.svelte` </a>is as simple as this one line.</p><pre><code>export let data: {auth: boolean, song: {id: string, url: string}};</code></pre><h3>Playlist Management</h3><p>Now I wrote more form actions for adding and removing songs from the playlist <a href="https://github.com/codebam/svelte-auth/blob/master/src/routes/playlist/%2Bpage.server.ts">here</a>.</p><pre><code class="language-typescript">import { redirect } from '@sveltejs/kit';
import tryLogin from '$lib/tryLogin';

export const actions = {
	add: async (event) =&gt; {
		if (await tryLogin(event.cookies.get('session'), event.platform?.env?.DB)) {
			const session = JSON.parse(event.cookies.get('session'));
			const email = session.email;
			const formdata = await event.request.formData();
			const v = new URL(formdata.get('url')).searchParams.get('v');
			await event.platform?.env?.DB.prepare('INSERT INTO Playlist VALUES (?, ?, ?, ?)')
				.bind(crypto.randomUUID(), email, v, Math.floor(new Date().getTime()))
				.all();
		}
		throw redirect(303, '/');
	},
	remove: async (event) =&gt; {
		if (await tryLogin(event.cookies.get('session'), event.platform?.env?.DB)) {
			const formdata = await event.request.formData();
			const id = formdata.get('id');
			await event.platform?.env?.DB.prepare('DELETE FROM Playlist WHERE id=?').bind(id).all();
		}
		throw redirect(303, '/');
	}
};</code></pre><h3>User Interface</h3><p>Now this allows us to add songs, we just need the interface for it. So I wrote the <a href="https://github.com/codebam/svelte-auth/blob/master/src/routes/%2Bpage.svelte">main page </a>to allow logged in users to submit songs.</p><pre><code class="language-svelte">&lt;script lang="ts"&gt;
	import { enhance } from '$app/forms';
	import YouTube from '$lib/svelte-youtube.svelte';
	export let data: { auth: boolean; song: { id: string; url: string } };
&lt;/script&gt;

{#if !data.auth}
	&lt;p&gt;Visit &lt;a href="https://seanbehan.ca/login"&gt;login&lt;/a&gt;&lt;/p&gt;
{/if}

&lt;YouTube
	on:end={() =&gt; document.getElementById('remove_song')?.click()}
	videoId={data.song.url}
	options={{ playerVars: { autoplay: 1 } }}
/&gt;

&lt;form style="display: none;" method="POST" action="/playlist?/remove" use:enhance&gt;
	&lt;input name="id" value={data.song.id} /&gt;
	&lt;button id="remove_song"&gt;remove&lt;/button&gt;
&lt;/form&gt;

{#if data.auth}
	&lt;form method="POST" action="/playlist?/add" use:enhance&gt;
		&lt;label
			&gt;Submit a song
			&lt;input name="url" type="url" placeholder="paste a youtube link" pattern=".*v=.*" /&gt;
		&lt;/label&gt;
		&lt;button&gt;Submit&lt;/button&gt;
	&lt;/form&gt;
	&lt;p&gt;&lt;a href="https://seanbehan.ca/logout"&gt;Log out&lt;/a&gt;&lt;/p&gt;
{/if}</code></pre><h3>Conclusion</h3><p>If you have any problems or you want to see the entire repo, the code is <a href="https://github.com/codebam/svelte-auth">here</a>.</p>]]></content:encoded>
	</item>
<item>
		<guid isPermaLink="true">https://seanbehan.ca/posts/llama2</guid>
		<title><![CDATA[Using Llama2 on Cloudflare Workers]]></title>
		<description><![CDATA[Wiring Workers AI into a Telegram bot: the AI binding, the model call, and getting a reply back.]]></description>
		<link>https://seanbehan.ca/posts/llama2</link>
		<pubDate>Thu, 12 Oct 2023 18:21:27 GMT</pubDate>
		<category>Cloudflare</category><category>serverless</category><category>TypeScript</category>
		<content:encoded><![CDATA[<h3>Introduction</h3><p>You might know I&#x27;ve made a Chat Bot <a href="https://github.com/codebam/cf-workers-telegram-bot">cf-workers-telegram-bot</a>.</p><p>Recently I added AI to it using Cloudflare AI. Here I&#x27;m going to show you how and how you too can make your own chatbot using Cloudflare AI.</p><h3>Cloudflare Workers AI Setup</h3><p>First you&#x27;ll want to add the AI section to your wrangler.toml.</p><pre><code class="language-toml">\[ai\]
binding = "AI"</code></pre><h3>Using the AI Model</h3><p>Then you can simply import</p><pre><code class="language-javascript">import { Ai } from '@cloudflare/ai';</code></pre><p>And you can use it like this</p><pre><code class="language-javascript">const ai = new Ai(env.AI);
prompt = 'hello world';
const result = await ai.run('@cf/meta/llama-2-7b-chat-int8', {
	prompt
});</code></pre><h3>Other Models and Tips</h3><p>That&#x27;s it!</p><p>There are other models you can use as well on Cloudflare AI so you should check them out here for other cool things you can do https://developers.cloudflare.com/workers-ai/models/ I&#x27;m hoping for a 70B model soon :) If you want to use this to make a chat bot make sure when you restore the prompt you wrap user input in <code>[INST] [/INST]</code> tags so llama2 knows what was user input and what it said.</p><p>If you want to try out my Chat Bot it&#x27;s on <a href="https://t.me/TuxRobot">Telegram</a>.</p>]]></content:encoded>
	</item>
	</channel>
</rss>