From 32fd2642b1e59f98aeed04322ce6fd86ce5ab377 Mon Sep 17 00:00:00 2001 From: Benjamin Palko Date: Thu, 22 Jan 2026 23:53:41 -0500 Subject: [PATCH] remove strapi and use mdsvex for blog --- package.json | 110 +++++++++--------- src/hooks.server.ts | 24 ++-- src/lib/components/BlogCard.svelte | 6 +- .../posts/godot-dotnet-with-spacetimedb.md | 82 +++++++++++++ src/lib/posts/index.ts | 35 ++++++ src/lib/posts/template.md.sample | 23 ++++ src/lib/posts/the-art-of-simple-design.md | 60 ++++++++++ src/routes/blog/+page.server.ts | 39 ++----- src/routes/blog/+page.svelte | 8 +- src/routes/blog/[slug]/+page.server.ts | 27 ----- src/routes/blog/[slug]/+page.svelte | 41 +++---- src/routes/blog/[slug]/+page.ts | 16 +++ src/routes/uploads/[slug]/+server.ts | 8 -- svelte.config.js | 20 ++-- 14 files changed, 319 insertions(+), 180 deletions(-) create mode 100644 src/lib/posts/godot-dotnet-with-spacetimedb.md create mode 100644 src/lib/posts/index.ts create mode 100644 src/lib/posts/template.md.sample create mode 100644 src/lib/posts/the-art-of-simple-design.md delete mode 100644 src/routes/blog/[slug]/+page.server.ts create mode 100644 src/routes/blog/[slug]/+page.ts delete mode 100644 src/routes/uploads/[slug]/+server.ts diff --git a/package.json b/package.json index 816b495..2326790 100644 --- a/package.json +++ b/package.json @@ -1,57 +1,57 @@ { - "name": "benjaminpalko.github.io", - "private": true, - "version": "0.0.1", - "type": "module", - "workspaces": [ - "strapi" - ], - "scripts": { - "dev": "vite dev | pino-pretty", - "compose:up": "docker compose --env-file .env -p strapi -f devops/docker-compose.dev.yml up -d ", - "compose:down": "docker compose -p strapi -f devops/docker-compose.dev.yml down", - "build": "vite build", - "preview": "vite preview", - "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "format": "prettier --write .", - "lint": "prettier --check . && eslint .", - "test:unit": "vitest", - "test": "npm run test:unit -- --run" - }, - "devDependencies": { - "@eslint/compat": "^1.2.8", - "@eslint/js": "^9.25.1", - "@sveltejs/adapter-node": "^5.2.12", - "@sveltejs/kit": "^2.20.7", - "@sveltejs/vite-plugin-svelte": "^5.0.3", - "@tailwindcss/vite": "^4.1.4", - "@types/nodemailer": "^6.4.17", - "autoprefixer": "^10.4.21", - "daisyui": "^5.0.28", - "eslint": "^9.25.1", - "eslint-config-prettier": "^10.1.2", - "eslint-plugin-svelte": "^3.5.1", - "globals": "^16.0.0", - "mdsvex": "^0.12.6", - "pino-pretty": "^13.0.0", - "prettier": "^3.5.3", - "prettier-plugin-svelte": "^3.3.3", - "prettier-plugin-tailwindcss": "^0.6.11", - "svelte": "^5.28.2", - "svelte-check": "^4.1.6", - "tailwindcss": "^4.1.4", - "typescript": "^5.8.3", - "typescript-eslint": "^8.31.0", - "vite": "^6.3.3", - "vitest": "^3.1.2" - }, - "dependencies": { - "@humanspeak/svelte-markdown": "^0.8.7", - "@lucide/svelte": "^0.516.0", - "@strapi/client": "^1.4.0", - "dayjs": "^1.11.13", - "nodemailer": "^6.10.1", - "pino": "^9.6.0" - } + "name": "benjaminpalko.ca", + "private": true, + "version": "0.0.1", + "type": "module", + "workspaces": [ + "strapi" + ], + "scripts": { + "dev": "vite dev | pino-pretty", + "compose:up": "docker compose --env-file .env -p strapi -f devops/docker-compose.dev.yml up -d ", + "compose:down": "docker compose -p strapi -f devops/docker-compose.dev.yml down", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "format": "prettier --write .", + "lint": "prettier --check . && eslint .", + "test:unit": "vitest", + "test": "npm run test:unit -- --run" + }, + "devDependencies": { + "@eslint/compat": "^1.2.8", + "@eslint/js": "^9.25.1", + "@sveltejs/adapter-node": "^5.2.12", + "@sveltejs/kit": "^2.20.7", + "@sveltejs/vite-plugin-svelte": "^5.0.3", + "@tailwindcss/vite": "^4.1.4", + "@types/nodemailer": "^6.4.17", + "autoprefixer": "^10.4.21", + "daisyui": "^5.0.28", + "eslint": "^9.25.1", + "eslint-config-prettier": "^10.1.2", + "eslint-plugin-svelte": "^3.5.1", + "globals": "^16.0.0", + "mdsvex": "^0.12.6", + "pino-pretty": "^13.0.0", + "prettier": "^3.5.3", + "prettier-plugin-svelte": "^3.3.3", + "prettier-plugin-tailwindcss": "^0.6.11", + "svelte": "^5.28.2", + "svelte-check": "^4.1.6", + "tailwindcss": "^4.1.4", + "typescript": "^5.8.3", + "typescript-eslint": "^8.31.0", + "vite": "^6.3.3", + "vitest": "^3.1.2" + }, + "dependencies": { + "@humanspeak/svelte-markdown": "^0.8.7", + "@lucide/svelte": "^0.516.0", + "@strapi/client": "^1.4.0", + "dayjs": "^1.11.13", + "nodemailer": "^6.10.1", + "pino": "^9.6.0" + } } diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 6fe773e..b9e1ca2 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,25 +1,15 @@ -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 }) => { - const errorId = randomUUID(); + const errorId = randomUUID(); - logger.error({ error, errorId, event, message, status }, 'Blyat'); + logger.error({ error, errorId, event, message, status }, 'Blyat'); - return { - message: 'An Internal Error Occurred', - errorId - }; + return { + message: 'An Internal Error Occurred', + errorId + }; }; -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; -}; diff --git a/src/lib/components/BlogCard.svelte b/src/lib/components/BlogCard.svelte index 07493a9..6a41e8f 100644 --- a/src/lib/components/BlogCard.svelte +++ b/src/lib/components/BlogCard.svelte @@ -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 @@
- {#each topics as topic (topic.id)} -
{topic.name}
+ {#each topics as topic (topic)} +
{topic}
{/each}
diff --git a/src/lib/posts/godot-dotnet-with-spacetimedb.md b/src/lib/posts/godot-dotnet-with-spacetimedb.md new file mode 100644 index 0000000..1bcd05c --- /dev/null +++ b/src/lib/posts/godot-dotnet-with-spacetimedb.md @@ -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 +``` diff --git a/src/lib/posts/index.ts b/src/lib/posts/index.ts new file mode 100644 index 0000000..5f81e68 --- /dev/null +++ b/src/lib/posts/index.ts @@ -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 + 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() + ) +} diff --git a/src/lib/posts/template.md.sample b/src/lib/posts/template.md.sample new file mode 100644 index 0000000..6b4b6b6 --- /dev/null +++ b/src/lib/posts/template.md.sample @@ -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}! 👋`); +} +``` + diff --git a/src/lib/posts/the-art-of-simple-design.md b/src/lib/posts/the-art-of-simple-design.md new file mode 100644 index 0000000..eb800e6 --- /dev/null +++ b/src/lib/posts/the-art-of-simple-design.md @@ -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; + createUser(user: User): Promise; + updateUser(user: User): Promise; + deleteUser(user_id: string): Promise; +} +``` + +```typescript +import { Client } from 'pg'; + +class PostgresUserAccessor implements IUserAccessor { + + private readonly Client _client; + + constructor PostgresUserAccessor() { + _client = new Client(); + } + + async findUser(id: string): Promise { + 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! diff --git a/src/routes/blog/+page.server.ts b/src/routes/blog/+page.server.ts index 5aabede..1007fe0 100644 --- a/src/routes/blog/+page.server.ts +++ b/src/routes/blog/+page.server.ts @@ -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 posts = await getPosts(); - try { - const { data, meta } = await strapi.Blogs(); - - 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 - }; - } catch (err) { - logger.error('Uh-oh!', err); - if (err instanceof Error) { - error(500, err); - } - } + return { + posts + }; + } catch (err) { + logger.error('Uh-oh!', err); + error(500); + } }; diff --git a/src/routes/blog/+page.svelte b/src/routes/blog/+page.svelte index 0c79a34..56c602c 100644 --- a/src/routes/blog/+page.svelte +++ b/src/routes/blog/+page.svelte @@ -11,14 +11,14 @@

Looks like I haven't posted anything yet!

{:else}
- {#each posts as post (post.id)} + {#each posts as post (post.slug)} {/each}
diff --git a/src/routes/blog/[slug]/+page.server.ts b/src/routes/blog/[slug]/+page.server.ts deleted file mode 100644 index ac1f8bd..0000000 --- a/src/routes/blog/[slug]/+page.server.ts +++ /dev/null @@ -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 - }; -}; diff --git a/src/routes/blog/[slug]/+page.svelte b/src/routes/blog/[slug]/+page.svelte index 53694cc..a015856 100644 --- a/src/routes/blog/[slug]/+page.svelte +++ b/src/routes/blog/[slug]/+page.svelte @@ -1,41 +1,26 @@
-

{dayjs(post.published).format('DD MMMM YYYY')}

-

{post.title}

+

{dayjs(data.meta.date).format('DD MMMM YYYY')}

+

{data.meta.title}

- {post.image.alt} + {data.meta.image.alt}
- +
+ + diff --git a/src/routes/blog/[slug]/+page.ts b/src/routes/blog/[slug]/+page.ts new file mode 100644 index 0000000..5c71601 --- /dev/null +++ b/src/routes/blog/[slug]/+page.ts @@ -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}`) + } +}; diff --git a/src/routes/uploads/[slug]/+server.ts b/src/routes/uploads/[slug]/+server.ts deleted file mode 100644 index bc568a0..0000000 --- a/src/routes/uploads/[slug]/+server.ts +++ /dev/null @@ -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; -}; diff --git a/svelte.config.js b/svelte.config.js index 6e721a5..0f95340 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -4,18 +4,18 @@ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; /** @type {import('@sveltejs/kit').Config} */ const config = { - // Consult https://svelte.dev/docs/kit/integrations - // for more information about preprocessors - preprocess: [vitePreprocess(), mdsvex()], + // Consult https://svelte.dev/docs/kit/integrations + // for more information about preprocessors + preprocess: [vitePreprocess(), mdsvex({ extensions: ['.md'] })], - kit: { - // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. - // If your environment is not supported, or you settled on a specific environment, switch out the adapter. - // See https://svelte.dev/docs/kit/adapters for more information about adapters. - adapter: adapter() - }, + kit: { + // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. + // If your environment is not supported, or you settled on a specific environment, switch out the adapter. + // See https://svelte.dev/docs/kit/adapters for more information about adapters. + adapter: adapter() + }, - extensions: ['.svelte', '.svx'] + extensions: ['.svelte', '.md'] }; export default config;