feat: Add tags to BlogPosts and allow posts to be found with them
This commit is contained in:
parent
26ecb457b7
commit
5ca7fc1ced
4 changed files with 65 additions and 23 deletions
|
|
@ -29,6 +29,25 @@ describe(`BlogController`, () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe(`getBlogPostBySlug`, () => {
|
||||||
|
it(`should return null when the post doesn't exist`, async () => {
|
||||||
|
// When
|
||||||
|
const shouldBeNull = await controller.getBlogPostBySlug('some-made-up-blog-post');
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(shouldBeNull).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should return the blog post when it exists`, async () => {
|
||||||
|
// When
|
||||||
|
const blogPost = await controller.getBlogPostBySlug('2023-02-03-vibe-check-10');
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(blogPost).not.toBeNull();
|
||||||
|
expect(blogPost.title).toBe('Vibe Check #10');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe(`Finding content by slug`, () => {
|
describe(`Finding content by slug`, () => {
|
||||||
describe(`Finding a blog post`, () => {
|
describe(`Finding a blog post`, () => {
|
||||||
// GIVEN
|
// GIVEN
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ interface BlogItem {
|
||||||
content: string;
|
content: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
content_type: 'blog' | 'book_review' | 'snout_street_studios';
|
content_type: 'blog' | 'book_review' | 'snout_street_studios';
|
||||||
|
tags?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BlogPostListItem extends BlogItem {
|
interface BlogPostListItem extends BlogItem {
|
||||||
|
|
@ -17,6 +18,7 @@ interface BlogPostListItem extends BlogItem {
|
||||||
date: string;
|
date: string;
|
||||||
book_review: boolean;
|
book_review: boolean;
|
||||||
preview: string;
|
preview: string;
|
||||||
|
tags: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BookReviewListItem extends BlogItem {
|
interface BookReviewListItem extends BlogItem {
|
||||||
|
|
@ -95,6 +97,45 @@ export class BlogController {
|
||||||
return allBlogPosts.slice(0, pageSize);
|
return allBlogPosts.slice(0, pageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getBlogPostBySlug(slug: string): Promise<BlogPostListItem | null> {
|
||||||
|
const blogPost = await this._markdownRepository.getBlogPostBySlug(slug);
|
||||||
|
if (blogPost) {
|
||||||
|
return this.blogPostToBlogPostListItem(blogPost);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBlogPostsByTags(tags: string[]): Promise<BlogPostListItem[]> {
|
||||||
|
const posts = await this.getAllBlogPosts();
|
||||||
|
const blogPosts = posts.filter((post) => post.content_type === 'blog') as BlogPostListItem[];
|
||||||
|
return blogPosts
|
||||||
|
.filter((post: BlogPostListItem) => post['tags']?.length > 0)
|
||||||
|
.filter((post: BlogPostListItem) => (post.tags as string[]).some((tag) => tags.includes(tag)));
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAnyKindOfContentBySlug(
|
||||||
|
slug: string
|
||||||
|
): Promise<BookReviewListItem | BlogPostListItem | SnoutStreetStudiosPostListItem | null> {
|
||||||
|
const blogPost = await this._markdownRepository.getBlogPostBySlug(slug);
|
||||||
|
if (blogPost) {
|
||||||
|
return this.blogPostToBlogPostListItem(blogPost);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bookReview = await this._markdownRepository.getBookReviewBySlug(slug);
|
||||||
|
if (bookReview) {
|
||||||
|
return this.bookReviewToBookReviewListItem(bookReview);
|
||||||
|
}
|
||||||
|
|
||||||
|
const snoutStreetStudiosPost = await this._markdownRepository.getSnoutStreetStudiosPostBySlug(slug);
|
||||||
|
|
||||||
|
if (snoutStreetStudiosPost) {
|
||||||
|
return this.snoutStreetStudiosPostToSnoutStreetStudiosPostListItem(snoutStreetStudiosPost);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private bookReviewToBookReviewListItem(bookReview: BookReview): BookReviewListItem {
|
private bookReviewToBookReviewListItem(bookReview: BookReview): BookReviewListItem {
|
||||||
return {
|
return {
|
||||||
book_review: true,
|
book_review: true,
|
||||||
|
|
@ -120,6 +161,7 @@ export class BlogController {
|
||||||
preview: blogPost.excerpt,
|
preview: blogPost.excerpt,
|
||||||
slug: blogPost.slug,
|
slug: blogPost.slug,
|
||||||
content_type: 'blog',
|
content_type: 'blog',
|
||||||
|
tags: blogPost.tags,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,26 +176,4 @@ export class BlogController {
|
||||||
content: post.html,
|
content: post.html,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAnyKindOfContentBySlug(
|
|
||||||
slug: string
|
|
||||||
): Promise<BookReviewListItem | BlogPostListItem | SnoutStreetStudiosPostListItem | null> {
|
|
||||||
const blogPost = await this._markdownRepository.getBlogPostBySlug(slug);
|
|
||||||
if (blogPost) {
|
|
||||||
return this.blogPostToBlogPostListItem(blogPost);
|
|
||||||
}
|
|
||||||
|
|
||||||
const bookReview = await this._markdownRepository.getBookReviewBySlug(slug);
|
|
||||||
if (bookReview) {
|
|
||||||
return this.bookReviewToBookReviewListItem(bookReview);
|
|
||||||
}
|
|
||||||
|
|
||||||
const snoutStreetStudiosPost = await this._markdownRepository.getSnoutStreetStudiosPostBySlug(slug);
|
|
||||||
|
|
||||||
if (snoutStreetStudiosPost) {
|
|
||||||
return this.snoutStreetStudiosPostToSnoutStreetStudiosPostListItem(snoutStreetStudiosPost);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ interface BlogPostFrontmatterValues {
|
||||||
slug: string;
|
slug: string;
|
||||||
date: Date;
|
date: Date;
|
||||||
author: string;
|
author: string;
|
||||||
|
tags?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BookReviewFrontmatterValues {
|
interface BookReviewFrontmatterValues {
|
||||||
|
|
@ -55,6 +56,7 @@ export class MarkdownRepository {
|
||||||
|
|
||||||
public static async singleton(forceRefresh = false): Promise<MarkdownRepository> {
|
public static async singleton(forceRefresh = false): Promise<MarkdownRepository> {
|
||||||
if (forceRefresh || !this._singleton) {
|
if (forceRefresh || !this._singleton) {
|
||||||
|
console.log(`[MarkdownRepository::singleton] Building MarkdownRepository singleton.`);
|
||||||
this._singleton = await MarkdownRepository.fromViteGlobImport(
|
this._singleton = await MarkdownRepository.fromViteGlobImport(
|
||||||
blogPostMetaGlobImport,
|
blogPostMetaGlobImport,
|
||||||
bookReviewsMetaGlobImport,
|
bookReviewsMetaGlobImport,
|
||||||
|
|
@ -90,7 +92,7 @@ export class MarkdownRepository {
|
||||||
author: markdownFile.frontmatter.author,
|
author: markdownFile.frontmatter.author,
|
||||||
date: markdownFile.frontmatter.date,
|
date: markdownFile.frontmatter.date,
|
||||||
fileName: filename,
|
fileName: filename,
|
||||||
tags: [],
|
tags: markdownFile.frontmatter.tags,
|
||||||
});
|
});
|
||||||
|
|
||||||
fileImports = [...fileImports, markdownFile];
|
fileImports = [...fileImports, markdownFile];
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ export class SnoutStreetStudiosApiGateway {
|
||||||
title: post.title,
|
title: post.title,
|
||||||
html: post.content,
|
html: post.content,
|
||||||
toJson: () => JSON.stringify(post),
|
toJson: () => JSON.stringify(post),
|
||||||
|
excerpt: post.
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue