From 57dd0a017ea5e077156081306db2a5b0247bb61e Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 12 Feb 2023 19:02:18 +0000 Subject: [PATCH] BlogEngine: Move the /new blog post endpoint to allow pre-rendering --- src/lib/blog/BlogController.test.ts | 17 +++- src/lib/blog/BlogController.ts | 18 ++-- src/lib/blog/markdown-repository.test.ts | 18 ++++ src/lib/blog/markdown-repository.ts | 15 +++- src/lib/floriferous/floriferous-game.spec.ts | 88 ++++++++++---------- src/routes/+page.ts | 2 +- src/routes/api/blog.json/+server.ts | 9 +- src/routes/api/blog/new.json/+server.ts | 8 ++ src/routes/blog/[slug]/+page.server.ts | 5 -- src/routes/blog/[slug]/+page.ts | 24 +++--- src/routes/blog/new/+page.ts | 4 +- 11 files changed, 127 insertions(+), 81 deletions(-) create mode 100644 src/routes/api/blog/new.json/+server.ts diff --git a/src/lib/blog/BlogController.test.ts b/src/lib/blog/BlogController.test.ts index be96cf1..a9d0810 100644 --- a/src/lib/blog/BlogController.test.ts +++ b/src/lib/blog/BlogController.test.ts @@ -1,5 +1,6 @@ -import { describe, it, beforeEach, beforeAll, expect } from 'vitest'; +import { describe, it, beforeEach, beforeAll, expect, afterEach } from 'vitest'; import { BlogController } from './BlogController.js'; +import { MarkdownRepository } from './markdown-repository.js'; describe(`BlogController`, () => { let controller: BlogController; @@ -71,4 +72,18 @@ describe(`BlogController`, () => { }); }); }); + + describe(`Creating a new blog post as a file`, () => { + let fileName: string; + let controller: BlogController; + + beforeEach(async () => { + fileName = 'some-made-up-blog-post.md'; + controller = await BlogController.singleton(); + }); + + afterEach(async () => { + await controller.markdownRepository.deleteBlogPostFile(fileName); + }); + }); }); diff --git a/src/lib/blog/BlogController.ts b/src/lib/blog/BlogController.ts index 4d29772..2a6a1af 100644 --- a/src/lib/blog/BlogController.ts +++ b/src/lib/blog/BlogController.ts @@ -28,17 +28,25 @@ interface BookReviewListItem { } export class BlogController { + private readonly _markdownRepository: MarkdownRepository; + static async singleton(): Promise { const markdownRepository = await MarkdownRepository.singleton(); return new BlogController(markdownRepository); } - constructor(private readonly markdownRepository: MarkdownRepository) {} + constructor(markdownRepository: MarkdownRepository) { + this._markdownRepository = markdownRepository; + } + + get markdownRepository(): MarkdownRepository { + return this._markdownRepository; + } async getAllBlogPosts(): Promise> { - const blogPosts = await this.markdownRepository.blogPosts; + const blogPosts = await this._markdownRepository.blogPosts; - const bookReviews = await this.markdownRepository.bookReviews; + const bookReviews = await this._markdownRepository.bookReviews; const blogPostListItems: BlogPostListItem[] = blogPosts.blogPosts.map((blogPost) => { return this.blogPostToBlogPostListItem(blogPost); @@ -78,12 +86,12 @@ export class BlogController { } async getBlogOrBookReviewBySlug(slug: string): Promise { - const blogPost = await this.markdownRepository.getBlogPostBySlug(slug); + const blogPost = await this._markdownRepository.getBlogPostBySlug(slug); if (blogPost) { return this.blogPostToBlogPostListItem(blogPost, true); } - const bookReview = await this.markdownRepository.getBookReviewBySlug(slug); + const bookReview = await this._markdownRepository.getBookReviewBySlug(slug); if (bookReview) { return this.bookReviewToBookReviewListItem(bookReview, true); } diff --git a/src/lib/blog/markdown-repository.test.ts b/src/lib/blog/markdown-repository.test.ts index e774082..2bbfecc 100644 --- a/src/lib/blog/markdown-repository.test.ts +++ b/src/lib/blog/markdown-repository.test.ts @@ -65,4 +65,22 @@ describe(`Blog MarkdownRepository`, () => { expect(markdownFile).toBeNull(); }); }); + + describe(`Deleting markdown files`, () => { + let repository: MarkdownRepository; + + beforeAll(async () => { + repository = await MarkdownRepository.fromViteGlobImport(blogPostImport, bookReviewImport); + }); + + it(`should throw an error if it attempts to delete a blog post file which does not exist`, async () => { + // GIVEN + const theFileName = 'non-existent-file.md'; + + // WHEN/THEN + expect(() => repository.deleteBlogPostMarkdownFile(theFileName)).toThrowError( + `Cannot delete file ${theFileName} as it does not exist` + ); + }); + }); }); diff --git a/src/lib/blog/markdown-repository.ts b/src/lib/blog/markdown-repository.ts index 4e7ff11..d2f196a 100644 --- a/src/lib/blog/markdown-repository.ts +++ b/src/lib/blog/markdown-repository.ts @@ -1,10 +1,16 @@ +import { resolve } from 'path'; +import { open } from 'fs'; + import { BlogPost } from './BlogPost.js'; import { MarkdownFile } from './MarkdownFile.js'; import { BlogPostSet } from './BlogPostSet.js'; import { BookReviewSet } from './BookReviewSet.js'; import { BookReview } from './BookReview.js'; -const blogPostMetaGlobImport = import.meta.glob('../../content/blog/*.md', { as: 'raw' }); +// We have to duplicate the `../..` here because import.meta must have a static string, +// and it (rightfully) cannot have dynamic locations +const blogPostMarkdownDirectory = `../../content/blog`; +const blogPostMetaGlobImport = import.meta.glob(`../../content/blog/*.md`, { as: 'raw' }); const bookReviewsMetaGlobImport = import.meta.glob('../../content/book-reviews/*.md', { as: 'raw' }); interface BlogPostFrontmatterValues { @@ -119,4 +125,11 @@ export class MarkdownRepository { getBookReviewBySlug(slug: string): BookReview | null { return this.bookReviews.bookReviews.find((bookReview) => bookReview.slug === slug) ?? null; } + + async deleteBlogPostMarkdownFile(fileName: string): Promise { + const file = this.blogPosts.blogPosts.find((blogPost) => blogPost.fileName === fileName); + if (file) { + const file = resolve(blogPostMarkdownDirectory, fileName); + } + } } diff --git a/src/lib/floriferous/floriferous-game.spec.ts b/src/lib/floriferous/floriferous-game.spec.ts index 2ac6fae..74dd52d 100644 --- a/src/lib/floriferous/floriferous-game.spec.ts +++ b/src/lib/floriferous/floriferous-game.spec.ts @@ -4,61 +4,59 @@ import { FloriferousGame } from './floriferous-game.js'; import { FloriferousPlayer } from './floriferous-player.js'; describe('FloriferousGame', () => { - const alice = new FloriferousPlayer({ - name: 'Alice', - score: 2, - rowAtEndOfGame: 0 - }); + const alice = new FloriferousPlayer({ + name: 'Alice', + score: 2, + rowAtEndOfGame: 0, + }); - const bob = new FloriferousPlayer({ - name: 'Bob', - score: 1, - rowAtEndOfGame: 1 - }); + const bob = new FloriferousPlayer({ + name: 'Bob', + score: 1, + rowAtEndOfGame: 1, + }); - const bobWithTwoPoints = new FloriferousPlayer({ - name: 'Bob', - score: 2, - rowAtEndOfGame: 1 - }); + const bobWithTwoPoints = new FloriferousPlayer({ + name: 'Bob', + score: 2, + rowAtEndOfGame: 1, + }); - it('Determines a winner', () => { - const game = new FloriferousGame(); + it('Determines a winner', () => { + const game = new FloriferousGame(); - // WHEN - game.addPlayer(alice); - game.addPlayer(bob); + // WHEN + game.addPlayer(alice); + game.addPlayer(bob); - // THEN - expect(game.winner).toBe('Alice'); - }); + // THEN + expect(game.winner).toBe('Alice'); + }); - it('Breaks a tie using the player closest to the top of the board', () => { - // GIVEN + it('Breaks a tie using the player closest to the top of the board', () => { + // GIVEN - const game = new FloriferousGame(); + const game = new FloriferousGame(); - // WHEN - game.addPlayer(alice); - game.addPlayer(bobWithTwoPoints); + // WHEN + game.addPlayer(alice); + game.addPlayer(bobWithTwoPoints); - // THEN - expect(game.winner).toBe('Alice'); - }); + // THEN + expect(game.winner).toBe('Alice'); + }); - it('Can give a pretty summary', () => { - // GIVEN - const game = new FloriferousGame({ - playedTs: new Date('2022-08-28T13:12Z'), - players: [alice, bob] - }); + it('Can give a pretty summary', () => { + // GIVEN + const game = new FloriferousGame({ + playedTs: new Date('2022-08-28T13:12Z'), + players: [alice, bob], + }); - // WHEN - const prettySummary = game.prettySummary; + // WHEN + const prettySummary = game.prettySummary; - // THEN - expect(prettySummary).toBe( - 'Sunday, 28 August 2022, 14:12: Alice won with 2 points. Bob: 1 point.' - ); - }); + // THEN + expect(prettySummary).toBe('Sunday, 28 August 2022 at 14:12: Alice won with 2 points. Bob: 1 point.'); + }); }); diff --git a/src/routes/+page.ts b/src/routes/+page.ts index 189f71e..d43d0cd 100644 --- a/src/routes/+page.ts +++ b/src/routes/+page.ts @@ -1 +1 @@ -export const prerender = true; +export const prerender = false; diff --git a/src/routes/api/blog.json/+server.ts b/src/routes/api/blog.json/+server.ts index 6525ddc..6ca180f 100644 --- a/src/routes/api/blog.json/+server.ts +++ b/src/routes/api/blog.json/+server.ts @@ -1,6 +1,5 @@ import { json } from '@sveltejs/kit'; -import type { RequestHandler } from './$types.js'; -import { BlogController } from '../../../lib/blog/BlogController'; +import { BlogController } from '../../../lib/blog/BlogController.js'; export const GET = async () => { try { @@ -19,9 +18,3 @@ export const GET = async () => { ); } }; - -export const POST: RequestHandler = async ({ getClientAddress }) => { - const address = await getClientAddress(); - console.log({ address }); - return json({ address }); -}; diff --git a/src/routes/api/blog/new.json/+server.ts b/src/routes/api/blog/new.json/+server.ts new file mode 100644 index 0000000..5cfb46a --- /dev/null +++ b/src/routes/api/blog/new.json/+server.ts @@ -0,0 +1,8 @@ +import { json } from '@sveltejs/kit'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const POST: RequestHandler = async ({ getClientAddress }) => { + const address = await getClientAddress(); + console.log({ address }); + return json({ address }); +}; diff --git a/src/routes/blog/[slug]/+page.server.ts b/src/routes/blog/[slug]/+page.server.ts index 48f490f..189f71e 100644 --- a/src/routes/blog/[slug]/+page.server.ts +++ b/src/routes/blog/[slug]/+page.server.ts @@ -1,6 +1 @@ -// Nearly four years ago I started writing blog posts in markdown. -// Now I've got a bunch of ragtag files, so pre-rendering them -// requires some Markdown parsing that I've not done yet. -// I'd love to get tis to be `true` -// 2023-02-06 Wilson export const prerender = true; diff --git a/src/routes/blog/[slug]/+page.ts b/src/routes/blog/[slug]/+page.ts index b40a913..c4184f9 100644 --- a/src/routes/blog/[slug]/+page.ts +++ b/src/routes/blog/[slug]/+page.ts @@ -1,17 +1,17 @@ import type { LoadEvent } from '@sveltejs/kit'; -import type { Post } from '$lib/Post'; +import type { Post } from '$lib/Post.js'; export async function load({ params, fetch }: LoadEvent): Promise<{ post: Post; date: Date }> { - const { slug } = params; - const { post } = await fetch(`/api/blog/${slug}.json`) - .then((res) => res.json()) - .catch((error) => { - console.error(error); - return { post: null }; - }); + const { slug } = params; + const { post } = await fetch(`/api/blog/${slug}.json`) + .then((res) => res.json()) + .catch((error) => { + console.error(error); + return { post: null }; + }); - return { - post, - date: new Date(post.date) - }; + return { + post, + date: new Date(post.date), + }; } diff --git a/src/routes/blog/new/+page.ts b/src/routes/blog/new/+page.ts index d834ff8..9a45289 100644 --- a/src/routes/blog/new/+page.ts +++ b/src/routes/blog/new/+page.ts @@ -1,9 +1,7 @@ import type { LoadEvent } from '@sveltejs/kit'; import { error } from '@sveltejs/kit'; -export async function load({ route, url }: LoadEvent) { - console.log({ route, url }); - +export async function load({ url }: LoadEvent) { if (url.hostname !== 'localhost') { return error(404, 'Not found'); }