BlogEngine: Move the /new blog post endpoint to allow pre-rendering

This commit is contained in:
Thomas 2023-02-12 19:02:18 +00:00
parent 6c48b3f188
commit 57dd0a017e
11 changed files with 127 additions and 81 deletions

View file

@ -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 { BlogController } from './BlogController.js';
import { MarkdownRepository } from './markdown-repository.js';
describe(`BlogController`, () => { describe(`BlogController`, () => {
let controller: 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);
});
});
}); });

View file

@ -28,17 +28,25 @@ interface BookReviewListItem {
} }
export class BlogController { export class BlogController {
private readonly _markdownRepository: MarkdownRepository;
static async singleton(): Promise<BlogController> { static async singleton(): Promise<BlogController> {
const markdownRepository = await MarkdownRepository.singleton(); const markdownRepository = await MarkdownRepository.singleton();
return new BlogController(markdownRepository); return new BlogController(markdownRepository);
} }
constructor(private readonly markdownRepository: MarkdownRepository) {} constructor(markdownRepository: MarkdownRepository) {
this._markdownRepository = markdownRepository;
}
get markdownRepository(): MarkdownRepository {
return this._markdownRepository;
}
async getAllBlogPosts(): Promise<Array<BlogPostListItem | BookReviewListItem>> { async getAllBlogPosts(): Promise<Array<BlogPostListItem | BookReviewListItem>> {
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) => { const blogPostListItems: BlogPostListItem[] = blogPosts.blogPosts.map((blogPost) => {
return this.blogPostToBlogPostListItem(blogPost); return this.blogPostToBlogPostListItem(blogPost);
@ -78,12 +86,12 @@ export class BlogController {
} }
async getBlogOrBookReviewBySlug(slug: string): Promise<BookReviewListItem | BlogPostListItem | null> { async getBlogOrBookReviewBySlug(slug: string): Promise<BookReviewListItem | BlogPostListItem | null> {
const blogPost = await this.markdownRepository.getBlogPostBySlug(slug); const blogPost = await this._markdownRepository.getBlogPostBySlug(slug);
if (blogPost) { if (blogPost) {
return this.blogPostToBlogPostListItem(blogPost, true); return this.blogPostToBlogPostListItem(blogPost, true);
} }
const bookReview = await this.markdownRepository.getBookReviewBySlug(slug); const bookReview = await this._markdownRepository.getBookReviewBySlug(slug);
if (bookReview) { if (bookReview) {
return this.bookReviewToBookReviewListItem(bookReview, true); return this.bookReviewToBookReviewListItem(bookReview, true);
} }

View file

@ -65,4 +65,22 @@ describe(`Blog MarkdownRepository`, () => {
expect(markdownFile).toBeNull(); 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`
);
});
});
}); });

View file

@ -1,10 +1,16 @@
import { resolve } from 'path';
import { open } from 'fs';
import { BlogPost } from './BlogPost.js'; import { BlogPost } from './BlogPost.js';
import { MarkdownFile } from './MarkdownFile.js'; import { MarkdownFile } from './MarkdownFile.js';
import { BlogPostSet } from './BlogPostSet.js'; import { BlogPostSet } from './BlogPostSet.js';
import { BookReviewSet } from './BookReviewSet.js'; import { BookReviewSet } from './BookReviewSet.js';
import { BookReview } from './BookReview.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' }); const bookReviewsMetaGlobImport = import.meta.glob('../../content/book-reviews/*.md', { as: 'raw' });
interface BlogPostFrontmatterValues { interface BlogPostFrontmatterValues {
@ -119,4 +125,11 @@ export class MarkdownRepository {
getBookReviewBySlug(slug: string): BookReview | null { getBookReviewBySlug(slug: string): BookReview | null {
return this.bookReviews.bookReviews.find((bookReview) => bookReview.slug === slug) ?? null; return this.bookReviews.bookReviews.find((bookReview) => bookReview.slug === slug) ?? null;
} }
async deleteBlogPostMarkdownFile(fileName: string): Promise<void> {
const file = this.blogPosts.blogPosts.find((blogPost) => blogPost.fileName === fileName);
if (file) {
const file = resolve(blogPostMarkdownDirectory, fileName);
}
}
} }

View file

@ -7,19 +7,19 @@ describe('FloriferousGame', () => {
const alice = new FloriferousPlayer({ const alice = new FloriferousPlayer({
name: 'Alice', name: 'Alice',
score: 2, score: 2,
rowAtEndOfGame: 0 rowAtEndOfGame: 0,
}); });
const bob = new FloriferousPlayer({ const bob = new FloriferousPlayer({
name: 'Bob', name: 'Bob',
score: 1, score: 1,
rowAtEndOfGame: 1 rowAtEndOfGame: 1,
}); });
const bobWithTwoPoints = new FloriferousPlayer({ const bobWithTwoPoints = new FloriferousPlayer({
name: 'Bob', name: 'Bob',
score: 2, score: 2,
rowAtEndOfGame: 1 rowAtEndOfGame: 1,
}); });
it('Determines a winner', () => { it('Determines a winner', () => {
@ -50,15 +50,13 @@ describe('FloriferousGame', () => {
// GIVEN // GIVEN
const game = new FloriferousGame({ const game = new FloriferousGame({
playedTs: new Date('2022-08-28T13:12Z'), playedTs: new Date('2022-08-28T13:12Z'),
players: [alice, bob] players: [alice, bob],
}); });
// WHEN // WHEN
const prettySummary = game.prettySummary; const prettySummary = game.prettySummary;
// THEN // THEN
expect(prettySummary).toBe( expect(prettySummary).toBe('Sunday, 28 August 2022 at 14:12: Alice won with 2 points. Bob: 1 point.');
'Sunday, 28 August 2022, 14:12: Alice won with 2 points. Bob: 1 point.'
);
}); });
}); });

View file

@ -1 +1 @@
export const prerender = true; export const prerender = false;

View file

@ -1,6 +1,5 @@
import { json } from '@sveltejs/kit'; import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types.js'; import { BlogController } from '../../../lib/blog/BlogController.js';
import { BlogController } from '../../../lib/blog/BlogController';
export const GET = async () => { export const GET = async () => {
try { 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 });
};

View file

@ -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 });
};

View file

@ -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; export const prerender = true;

View file

@ -1,5 +1,5 @@
import type { LoadEvent } from '@sveltejs/kit'; 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 }> { export async function load({ params, fetch }: LoadEvent): Promise<{ post: Post; date: Date }> {
const { slug } = params; const { slug } = params;
@ -12,6 +12,6 @@ export async function load({ params, fetch }: LoadEvent): Promise<{ post: Post;
return { return {
post, post,
date: new Date(post.date) date: new Date(post.date),
}; };
} }

View file

@ -1,9 +1,7 @@
import type { LoadEvent } from '@sveltejs/kit'; import type { LoadEvent } from '@sveltejs/kit';
import { error } from '@sveltejs/kit'; import { error } from '@sveltejs/kit';
export async function load({ route, url }: LoadEvent) { export async function load({ url }: LoadEvent) {
console.log({ route, url });
if (url.hostname !== 'localhost') { if (url.hostname !== 'localhost') {
return error(404, 'Not found'); return error(404, 'Not found');
} }