Skip to content

Commit

Permalink
feat: rss + atom (#1092)
Browse files Browse the repository at this point in the history
  • Loading branch information
abvthecity authored Jun 29, 2024
1 parent ddad730 commit 359c8ed
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 6 deletions.
2 changes: 1 addition & 1 deletion packages/ui/app/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export { useSetThemeColors } from "./docs/ThemeProvider";
export * from "./next-app/DocsPage";
export { NextApp } from "./next-app/NextApp";
export { getBreadcrumbList } from "./next-app/utils/getBreadcrumbList";
export { getDefaultSeoProps } from "./next-app/utils/getSeoProp";
export { getDefaultSeoProps, getFrontmatter } from "./next-app/utils/getSeoProp";
export { ApiDefinitionResolver } from "./resolver/ApiDefinitionResolver";
export { ApiTypeResolver } from "./resolver/ApiTypeResolver";
export * from "./resolver/types";
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/app/src/next-app/utils/getBreadcrumbList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export function getBreadcrumbList(
if (FernNavigation.isPage(node)) {
const pageId = FernNavigation.utils.getPageId(node);
if (pageId != null && pages[pageId] != null) {
const frontmatter = getFrontmatter(pages[pageId].markdown);
const [frontmatter] = getFrontmatter(pages[pageId].markdown);
if (frontmatter["jsonld:breadcrumb"] != null) {
const breadcrumb = JsonLd.BreadcrumbListSchema.safeParse(frontmatter["jsonld:breadcrumb"]);
if (breadcrumb.success) {
Expand Down
8 changes: 4 additions & 4 deletions packages/ui/app/src/next-app/utils/getSeoProp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ function getFile(fileOrUrl: DocsV1Read.FileIdOrUrl, files: Record<string, DocsV1
});
}

export function getFrontmatter(content: string): FernDocsFrontmatter {
export function getFrontmatter(content: string): [FernDocsFrontmatter, string] {
try {
const gm = grayMatter(content);
return gm.data;
return [gm.data, gm.content];
} catch (e) {
return {};
return [{}, content];
}
}

Expand Down Expand Up @@ -51,7 +51,7 @@ export function getDefaultSeoProps(
let ogMetadata: DocsV1Read.MetadataConfig = metadata ?? {};

if (pageId != null && pages[pageId]) {
const frontmatter = getFrontmatter(pages[pageId].markdown);
const [frontmatter] = getFrontmatter(pages[pageId].markdown);
ogMetadata = { ...ogMetadata, ...frontmatter };

// retrofit og:image
Expand Down
2 changes: 2 additions & 0 deletions packages/ui/docs-bundle/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ const nextConfig = {
{ source: "/_vercel/:path*", destination: "/_vercel/:path*" },
{ source: "/robots.txt", destination: "/api/fern-docs/robots.txt" },
{ source: "/sitemap.xml", destination: "/api/fern-docs/sitemap.xml" },
{ source: "/:path*.rss", destination: "/api/fern-docs/changelog?format=rss&path=:path*" },
{ source: "/:path*.atom", destination: "/api/fern-docs/changelog?format=atom&path=:path*" },

// backwards compatibility with currently deployed FDR
{ source: "/api/revalidate-all", destination: "/api/fern-docs/revalidate-all" },
Expand Down
1 change: 1 addition & 0 deletions packages/ui/docs-bundle/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"@vercel/edge-config": "^1.1.0",
"@workos-inc/node": "^6.1.0",
"cssnano": "^6.0.3",
"feed": "^4.2.2",
"jose": "^5.2.3",
"jsonpath": "^1.1.1",
"next": "^14.2.3",
Expand Down
109 changes: 109 additions & 0 deletions packages/ui/docs-bundle/src/pages/api/fern-docs/changelog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { FernNavigation } from "@fern-api/fdr-sdk";
import { NodeCollector } from "@fern-api/fdr-sdk/navigation";
import { assertNever } from "@fern-ui/core-utils";
import { getFrontmatter } from "@fern-ui/ui";
import { Feed, Item } from "feed";
import { NextApiRequest, NextApiResponse } from "next";
import { NextResponse } from "next/server";
import { buildUrlFromApiNode } from "../../../utils/buildUrlFromApi";
import { loadWithUrl } from "../../../utils/loadWithUrl";
import { getXFernHostNode } from "../../../utils/xFernHost";

export const revalidate = 60 * 60 * 24;

export default async function responseApiHandler(req: NextApiRequest, res: NextApiResponse): Promise<unknown> {
if (req.method !== "GET") {
return res.status(400).end();
}

let path = req.query["path"];
const format = req.query["format"] ?? "rss";

if (typeof path !== "string" || typeof format !== "string") {
return res.status(400).end();
}

if (path.startsWith("/")) {
path = path.slice(1);
}

path = decodeURIComponent(path);

const xFernHost = getXFernHostNode(req);
const headers = new Headers();
headers.set("x-fern-host", xFernHost);

const url = buildUrlFromApiNode(xFernHost, req);
const docs = await loadWithUrl(url);

if (docs == null) {
return res.status(404).end();
}

const root = FernNavigation.utils.convertLoadDocsForUrlResponse(docs);
const collector = NodeCollector.collect(root);

const node = collector.slugMap.get(path);

if (node?.type !== "changelog") {
return new NextResponse(null, { status: 404 });
}

const link = `https://${xFernHost}/${node.slug}`;

const feed = new Feed({
id: link,
link,
title: node.title,
copyright: `All rights reserved ${new Date().getFullYear()}`,
generator: "buildwithfern.com",
});

node.children.forEach((year) => {
year.children.forEach((month) => {
month.children.forEach((entry) => {
const item: Item = {
title: entry.title,
link: `https://${xFernHost}/${entry.slug}`,
date: new Date(entry.date),
};

const markdown = docs.definition.pages[entry.pageId].markdown;
if (markdown != null) {
const [frontmatter, content] = getFrontmatter(markdown);
item.description = frontmatter.description ?? frontmatter.subtitle ?? frontmatter.excerpt;
item.content = content;

if (frontmatter.image != null) {
item.image = { url: frontmatter.image };
} else if (frontmatter["og:image"] != null) {
if (frontmatter["og:image"].type === "url") {
item.image = { url: frontmatter["og:image"].value };
} else if (frontmatter["og:image"].type === "fileId") {
const fileId = frontmatter["og:image"].value;
const file = docs.definition.files[fileId];
if (file != null) {
item.image = { url: file };
}
} else {
assertNever(frontmatter["og:image"]);
}
}
}

feed.addItem(item);
});
});
});

if (format === "json") {
headers.set("Content-Type", "application/json");
return res.json(feed.json1());
} else if (format === "atom") {
headers.set("Content-Type", "application/atom+xml");
return res.send(feed.atom1());
} else {
headers.set("Content-Type", "application/rss+xml");
return res.send(feed.rss2());
}
}
17 changes: 17 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 359c8ed

Please sign in to comment.