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

View file

@ -28,17 +28,25 @@ interface BookReviewListItem {
}
export class BlogController {
private readonly _markdownRepository: MarkdownRepository;
static async singleton(): Promise<BlogController> {
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<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) => {
return this.blogPostToBlogPostListItem(blogPost);
@ -78,12 +86,12 @@ export class BlogController {
}
async getBlogOrBookReviewBySlug(slug: string): Promise<BookReviewListItem | BlogPostListItem | null> {
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);
}

View file

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

View file

@ -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<void> {
const file = this.blogPosts.blogPosts.find((blogPost) => blogPost.fileName === fileName);
if (file) {
const file = resolve(blogPostMarkdownDirectory, fileName);
}
}
}

View file

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

View file

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

View file

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

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;

View file

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

View file

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