remove strapi and use mdsvex for blog
Some checks failed
Deployment / Deploy website (push) Successful in 3m21s
Deployment / Deploy strapi (push) Failing after 1m12s

This commit is contained in:
Benjamin Palko 2026-01-22 23:53:41 -05:00
parent 4673e8dbe6
commit 32fd2642b1
14 changed files with 319 additions and 180 deletions

View file

@ -1,5 +1,5 @@
{
"name": "benjaminpalko.github.io",
"name": "benjaminpalko.ca",
"private": true,
"version": "0.0.1",
"type": "module",

View file

@ -1,7 +1,5 @@
import { env } from '$env/dynamic/private';
import { logger } from '$lib/server/logger';
import { Strapi } from '$lib/server/strapi';
import type { Handle, HandleServerError } from '@sveltejs/kit';
import type { HandleServerError } from '@sveltejs/kit';
import { randomUUID } from 'crypto';
export const handleError: HandleServerError = async ({ error, event, message, status }) => {
@ -15,11 +13,3 @@ export const handleError: HandleServerError = async ({ error, event, message, st
};
};
export const handle: Handle = async ({ event, resolve }) => {
event.locals = {
strapi: new Strapi(`http://${env.STRAPI_HOST}:${env.STRAPI_PORT}`, env.STRAPI_TOKEN)
};
const response = await resolve(event);
return response;
};

View file

@ -8,7 +8,7 @@
'img-alt': string | null;
title: string;
published: string;
topics: { id: number; name: string }[];
topics: string[];
}
let { id, img, 'img-alt': alt, title, published, topics }: Props = $props();
@ -19,8 +19,8 @@
<div class="card">
<figure class="rounded-box group">
<div class="absolute top-4 left-4 z-10 flex gap-2">
{#each topics as topic (topic.id)}
<div class="badge badge-soft badge-info">{topic.name}</div>
{#each topics as topic (topic)}
<div class="badge badge-soft badge-info">{topic}</div>
{/each}
</div>
<div class="absolute z-10 opacity-0 transition-opacity group-hover:opacity-75">

View file

@ -0,0 +1,82 @@
---
title: Godot .NET with SpacetimeDB
description: First post.
image:
url: https://spacetimedb.com/meta-tags.png
alt: SpacetimeDB
date: '2026-1-22'
categories:
- development
published: true
---
I've been on and off again experimenting with Godot since the great Unity [controversy of 2023.](https://www.geekwire.com/2023/heres-why-so-many-video-game-developers-are-suddenly-abandoning-the-unity-engine/) At the time Godot came up pretty quickly as the open-source alternative, I'll spare you the history lesson we are here to talk SpacetimeDB! If you found this article I assume you already know what SpacetimeDB is.
# Setting up our workspace
Before we get into any programming we are going to do some setup on our project workspace to help things along, we will be thankful for this later! I'm going to be painfully explicit in the details here so bare with me.
## Create a project directory
We are going to sidestep creating the root project directory with Godot, this will become apparent as to why later but for now just trust me! 😉
```bash
mkdir massive
```
## Init Git
```bash
git init
```
## Set tool version with mise
We are going to use [mise-en-place](https://mise.jdx.dev/) to handle our tool versions at the project level, this is a great tool that I now use by default for any new project Im working on. It allows us to keep our tool versions independent of our global settings.
```bash
mise use dotnet@8 bun@latest
```
> This will generate a `mise.toml` file in our project root.
The first tool is obvious, we are writing in C# so of course we need dotnet! Using bun however, let me explain.
## Create a new Godot project
## Create docker-compose
```yaml
services:
spacetime:
image: clockworklabs/spacetime
ports:
- 3000:3000
command: start
```
## Create package.json
```json
{
"name": "massive",
"scripts": {
"database:up": "docker compose up -d",
"database:down": "docker compose down",
"database:publish": "spacetime publish --project-path server massive",
"database:generate": "spacetime generate --lang csharp --out-dir client/module_bindings --project-path server"
},
"devDependencies": {
"@types/bun": "latest"
}
}
```
## .editorconfig
```editorconfig
root = true
[*]
charset = utf-8
```

35
src/lib/posts/index.ts Normal file
View file

@ -0,0 +1,35 @@
export type Categories = 'development' | 'work' | 'life';
export type Post = {
title: string
slug: string
description: string
image: {
url: string;
alt: string;
}
date: string
categories: Categories[]
published: boolean
}
export async function getPosts() {
const posts: Post[] = []
const paths = import.meta.glob('/src/lib/posts/*.md', { eager: true })
for (const path in paths) {
const file = paths[path]
const slug = path.split('/').at(-1)?.replace('.md', '')
if (file && typeof file === 'object' && 'metadata' in file && slug) {
const metadata = file.metadata as Omit<Post, 'slug'>
const post = { ...metadata, slug } satisfies Post
if (post.published) posts.push(post)
}
}
return posts.sort((first, second) =>
new Date(second.date).getTime() - new Date(first.date).getTime()
)
}

View file

@ -0,0 +1,23 @@
---
title: First post
description: First post.
image:
url: https://image.url
alt: This is an image
date: '2023-4-14'
categories:
- sveltekit
- svelte
published: true
---
## Markdown
Hey friends! 👋
```ts
function greet(name: string) {
console.log(`Hey ${name}! 👋`);
}
```

View file

@ -0,0 +1,60 @@
---
title: The Art of Simple Design
description: Functional Decomposition as a Method of handling Contextual Shifts during project development
image:
url: https://images.unsplash.com/photo-1766811474703-6e86727d7f95?q=80&w=687&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D
alt: This is an image
date: '2026-1-22'
categories:
- development
published: true
---
Project degradation as a result of contextual shifts during development is a common occurrence, contextual shifts are a natural part of the project development process, and we should always try to account for them in our design and as we develop. Some design frameworks like IDesign seek to solve this through functional decomposition, by reducing a project to a set of functional interactions, i.e a data access calls, business logic, orchestration, etc... This is certainly the correct path, but in the process of trying to develop a perfect solution, often falls into the pit of overdesign with strict naming conventions and complicated architecture. This actively becomes a hindrance to development cadence, the problem we were seeking to fix.
A strong design methodology should not be a strict instruction manual, but an informative pamphlet. It should impart a new perspective on the same problems we have seen a million times, and give new tools to use.
```typescript
interface User {
id: string;
name: string;
email: string;
hashed_password: string;
}
interface IUserAccessor {
findUser(user_id: string): Promise<User>;
createUser(user: User): Promise<User>;
updateUser(user: User): Promise<User>;
deleteUser(user_id: string): Promise<boolean>;
}
```
```typescript
import { Client } from 'pg';
class PostgresUserAccessor implements IUserAccessor {
private readonly Client _client;
constructor PostgresUserAccessor() {
_client = new Client();
}
async findUser(id: string): Promise<User> {
await _client.connect()
const res = await client.query("SELECT * FROM users WHERE users.id = '$1::text'", [id]);
await _client.end()
return res.rows[0];
}
```
Over the years, between the projects I have worked on, I started to notice that teams tended to gravitate towards one side of a spectrum.
On one side you have the people who don't follow design patterns, this is the wild west and anything goes, maybe they don't know what patterns to use or even care that they exist? Their architecture is based on vibes, or what has come before, it tends to be disorganized and inconsistent.
On the other side of the spectrum are the people who are design pattern evangelists, they have either seen these projects and understand the importance of design, or are from a theoretical/academic background. They know their pattern(s) well, but perhaps are too vigorous in their application, this can result in an over-designed mess. What this looks like
They try so hard to strictly adhere to a chosen design pattern, but either out of misunderstanding or overcommitment, their code becomes rigid and unnatural. Think of it like this, Bridges are a great structure for traversing difficult terrain, but they have their uses. If I was trying to travel upstream on a winding river, I would not build a bridge straight up the river, when completed it may be the fastest option but it would certainly not be cheap or sensible, a boat would be a better option. Now if the context changes and I am trying to traverse from one side of this river to the other? A boat still works, but now the bridge is making a lot more sense! My point is, if all you know is bridge design then everything starts to look like a bridge problem, or... something something hammer and nails.
Perhaps this leads into another topic about overspecialization and the benefits of generalization in a specialized world. The greatest tools you can have on your belt are new perspectives!

View file

@ -1,34 +1,17 @@
import { logger } from '$lib/server/logger';
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { getPosts } from '$lib/posts';
export const load: PageServerLoad = async ({ locals }) => {
const { strapi } = locals;
export const load: PageServerLoad = async () => {
try {
const { data, meta } = await strapi.Blogs();
const posts = await getPosts();
return {
posts: data.map((post) => ({
id: post.documentId,
image: {
url: post.Header.formats.medium.url,
alt: post.Header.alternativeText
},
title: post.Title,
body: post.Body,
topics: post.Topics.map((topic: { id: string; Topic: string }) => ({
id: topic.id,
name: topic.Topic
})),
published: post.publishedAt
})),
meta
posts
};
} catch (err) {
logger.error('Uh-oh!', err);
if (err instanceof Error) {
error(500, err);
}
error(500);
}
};

View file

@ -11,14 +11,14 @@
<h3 class="text-center text-lg font-semibold">Looks like I haven't posted anything yet!</h3>
{:else}
<div class="mx-auto grid gap-8 px-3 py-3 sm:px-8 sm:py-4 lg:grid-cols-2">
{#each posts as post (post.id)}
{#each posts as post (post.slug)}
<BlogCard
id={post.id}
id={post.slug}
img={post.image.url}
img-alt={post.image.alt}
title={post.title}
published={post.published}
topics={post.topics}
published={post.date}
topics={post.categories}
/>
{/each}
</div>

View file

@ -1,27 +0,0 @@
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ locals, params }) => {
const { slug } = params;
const { strapi } = locals;
const { data: post, meta } = await strapi.Blog(slug);
return {
post: {
id: post.documentId,
image: {
url: post.Header.url,
alt: post.Header.alternativeText,
caption: post.Header.caption
},
title: post.Title,
body: post.Body,
topics: post.Topics.map((topic: { id: string; Topic: string }) => ({
id: topic.id,
name: topic.Topic
})),
published: post.publishedAt
},
meta
};
};

View file

@ -1,41 +1,26 @@
<script lang="ts">
import BlockQuote from '$lib/markdown/renderers/BlockQuote.svelte';
import Code from '$lib/markdown/renderers/Code.svelte';
import Heading from '$lib/markdown/renderers/Heading.svelte';
import Link from '$lib/markdown/renderers/Link.svelte';
import List from '$lib/markdown/renderers/List.svelte';
import OrderedListItem from '$lib/markdown/renderers/OrderedListItem.svelte';
import Paragraph from '$lib/markdown/renderers/Paragraph.svelte';
import UnorderedListItem from '$lib/markdown/renderers/UnorderedListItem.svelte';
import SvelteMarkdown from '@humanspeak/svelte-markdown';
import Image from '$lib/components/Image.svelte';
import dayjs from 'dayjs';
import type { PageProps } from './$types';
import Image from '$lib/components/Image.svelte';
let { data }: PageProps = $props();
let post = $derived(data.post);
</script>
<div class="flex flex-col items-center gap-4">
<div class="w-full max-w-5xl px-4">
<p class="text-gray-500">{dayjs(post.published).format('DD MMMM YYYY')}</p>
<h1 class="text-4xl font-light">{post.title}</h1>
<p class="text-gray-500">{dayjs(data.meta.date).format('DD MMMM YYYY')}</p>
<h1 class="text-4xl font-light">{data.meta.title}</h1>
</div>
<Image src={post.image.url} alt={post.image.alt} caption={post.image.caption} />
<Image src={data.meta.image.url} alt={data.meta.image.alt} />
<div class="w-full max-w-5xl px-4">
<SvelteMarkdown
source={data.post.body}
renderers={{
link: Link,
list: List,
orderedlistitem: OrderedListItem,
unorderedlistitem: UnorderedListItem,
code: Code,
blockquote: BlockQuote,
heading: Heading,
paragraph: Paragraph
}}
/>
<data.content class="content" />
</div>
</div>
<style>
.content {
* code {
@apply bg-base-200 border-base-300 text-base-content rounded-lg border-l-4 p-4 text-gray-700;
}
}
</style>

View file

@ -0,0 +1,16 @@
import { error } from '@sveltejs/kit';
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ params }) => {
try {
const post = await import(`../../../lib/posts/${params.slug}.md`)
return {
content: post.default,
meta: post.metadata
}
} catch (e) {
error(404, `Could not find ${params.slug}`)
}
};

View file

@ -1,8 +0,0 @@
import type { RequestHandler } from './$types';
export const GET: RequestHandler = async ({ locals, params }) => {
const { slug } = params;
const response = await fetch(`${locals.strapi.uploads}/${slug}`);
return response;
};

View file

@ -6,7 +6,7 @@ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
const config = {
// Consult https://svelte.dev/docs/kit/integrations
// for more information about preprocessors
preprocess: [vitePreprocess(), mdsvex()],
preprocess: [vitePreprocess(), mdsvex({ extensions: ['.md'] })],
kit: {
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
@ -15,7 +15,7 @@ const config = {
adapter: adapter()
},
extensions: ['.svelte', '.svx']
extensions: ['.svelte', '.md']
};
export default config;