BlogEngine: Move the /new blog post endpoint to allow pre-rendering
This commit is contained in:
parent
6c48b3f188
commit
57dd0a017e
11 changed files with 127 additions and 81 deletions
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export const prerender = true;
|
||||
export const prerender = false;
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
};
|
||||
|
|
|
|||
8
src/routes/api/blog/new.json/+server.ts
Normal file
8
src/routes/api/blog/new.json/+server.ts
Normal 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 });
|
||||
};
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue