Skip to content

Commit 48daade

Browse files
committed
Expose case studies and product updates in RSS feed
1 parent 0656f2f commit 48daade

File tree

3 files changed

+69
-29
lines changed

3 files changed

+69
-29
lines changed

packages/web/docs/src/app/blog/(posts)/graphql-request-cancellation/page.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ flowchart
176176
id31["Load total post likes (Post.likeCount)"]
177177
id32["Load comments for each post (Post.comments)"]
178178
id4["Load author for each comments (Comment.author)"]
179-
id5["Load author avataer for each comment author (User.avatar)"]
179+
id5["Load author avatar for each comment author (User.avatar)"]
180180
181181
id1 --> id21
182182
id1 --> id22
@@ -199,7 +199,7 @@ flowchart
199199
id31["Load total post likes (Post.likeCount)"]
200200
id32["Load comments for each post (Post.comments)"]
201201
id4["Load author for each comments (Comment.author)"]
202-
id5["Load author avataer for each comment author (User.avatar)"]
202+
id5["Load author avatar for each comment author (User.avatar)"]
203203
204204
id1 --> id21
205205
id1 --> id22

packages/web/docs/src/app/blog/feed.xml/route.ts

Lines changed: 51 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,49 @@
22
import RSS from 'rss';
33
import { getPageMap } from '@theguild/components/server';
44
import { AuthorId, authors } from '../../../authors';
5-
import { isBlogPost } from '../blog-types';
5+
import { pagesDepthFirst } from '../../../mdx-types';
6+
import { coerceCaseStudyToBlog } from '../../case-studies/coerce-case-studies-to-blogs';
7+
import { isCaseStudy } from '../../case-studies/isCaseStudyFile';
8+
import { BlogFrontmatter, BlogPostFile, isBlogPost } from '../blog-types';
69

7-
function getAuthor(name: string) {
8-
const author = authors[name as AuthorId]?.name;
9-
return author ?? name;
10+
function getAuthor(frontmatterAuthors: BlogFrontmatter['authors']): string {
11+
const first = Array.isArray(frontmatterAuthors) ? frontmatterAuthors[0] : frontmatterAuthors;
12+
13+
if (typeof first === 'string') {
14+
const author = authors[first as AuthorId];
15+
return author ? author.name : 'Unknown Author';
16+
}
17+
18+
return first.name;
1019
}
1120

21+
export const dynamic = 'force-static';
22+
export const config = { runtime: 'edge' };
23+
1224
export async function GET() {
13-
const [_meta, _indexPage, ...pageMap] = await getPageMap('/blog');
14-
const allPosts = pageMap
15-
.filter(isBlogPost)
16-
.map(
17-
item =>
18-
({
19-
title: item.frontMatter.title,
20-
date: new Date(item.frontMatter.date),
21-
url: `https://the-guild.dev/graphql/hive${item.route}`,
22-
description: (item.frontMatter as any).description ?? '',
23-
author: getAuthor(
24-
typeof item.frontMatter.authors === 'string'
25-
? item.frontMatter.authors
26-
: item.frontMatter.authors.at(0)!,
27-
),
28-
categories: Array.isArray(item.frontMatter.tags)
29-
? item.frontMatter.tags
30-
: [item.frontMatter.tags],
31-
}) satisfies RSS.ItemOptions,
32-
)
33-
.sort((a, b) => b.date.getTime() - a.date.getTime());
25+
let allPosts: RSS.ItemOptions[] = [];
26+
27+
const [_meta, _indexPage, ...pages] = await getPageMap('/');
28+
for (const page of pagesDepthFirst(pages)) {
29+
const route = (page && 'route' in page && page.route) || '';
30+
const [dir, name] = route.split('/').filter(Boolean);
31+
if (!name) continue;
32+
switch (dir) {
33+
case 'blog':
34+
case 'product-updates':
35+
if (isBlogPost(page)) allPosts.push(toRssItem(page));
36+
break;
37+
case 'case-studies':
38+
if (isCaseStudy(page)) allPosts.push(toRssItem(coerceCaseStudyToBlog(page)));
39+
break;
40+
}
41+
}
42+
43+
if (allPosts.length === 0) {
44+
throw new Error('No blog posts found for RSS feed');
45+
}
46+
47+
allPosts = allPosts.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
3448

3549
const feed = new RSS({
3650
title: 'Hive Blog',
@@ -49,5 +63,15 @@ export async function GET() {
4963
});
5064
}
5165

52-
export const dynamic = 'force-static';
53-
export const config = { runtime: 'edge' };
66+
function toRssItem(blogPost: BlogPostFile): RSS.ItemOptions {
67+
return {
68+
title: blogPost.frontMatter.title,
69+
date: new Date(blogPost.frontMatter.date),
70+
url: `https://the-guild.dev/graphql/hive${blogPost.route}`,
71+
description: blogPost.frontMatter.description ?? '',
72+
author: getAuthor(blogPost.frontMatter.authors),
73+
categories: Array.isArray(blogPost.frontMatter.tags)
74+
? blogPost.frontMatter.tags
75+
: [blogPost.frontMatter.tags],
76+
};
77+
}

packages/web/docs/src/mdx-types.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,19 @@ export type MdxFile<FrontMatterType> = {
1313
* TODO: This should be exported from `nextra` and `@theguild/components`
1414
*/
1515
export type PageMapItem = Awaited<ReturnType<typeof getPageMap>>[number];
16+
17+
export type FolderItem = Extract<PageMapItem, { children: PageMapItem[] }>;
18+
19+
export function isFolder(item: PageMapItem): item is FolderItem {
20+
return !!item && typeof item === 'object' && 'children' in item;
21+
}
22+
23+
export function* pagesDepthFirst(items: PageMapItem[]): Generator<PageMapItem> {
24+
for (const item of items) {
25+
if (isFolder(item)) {
26+
yield* pagesDepthFirst(item.children);
27+
} else {
28+
yield item;
29+
}
30+
}
31+
}

0 commit comments

Comments
 (0)