Skip to content
This repository has been archived by the owner on Nov 20, 2024. It is now read-only.

Commit

Permalink
fix: atom missing authors (#383)
Browse files Browse the repository at this point in the history
  • Loading branch information
DanSnow authored Apr 22, 2024
1 parent f034915 commit 8206a23
Show file tree
Hide file tree
Showing 8 changed files with 308 additions and 116 deletions.
5 changes: 5 additions & 0 deletions .changeset/rare-shrimps-obey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@storipress/karbon': patch
---

fix: missing author in atom feed
1 change: 1 addition & 0 deletions packages/karbon/src/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ export { getDesk, listDesks } from './runtime/api/desk'
export { getTag, listTags } from './runtime/api/tag'
export { getAuthor, listAuthors } from './runtime/api/author'
export { getResources, payloadScopes } from './runtime/api/sitemap'
export * from './runtime/lib/feed'
3 changes: 2 additions & 1 deletion packages/karbon/src/runtime/api/feed.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { gql } from '@apollo/client/core/index.js'
import { useStoripressClient } from '../composables/storipress-client'
import type { _NormalizeArticle } from './normalize-article'

const GetDesk = gql`
query GetDesk($slug: String) {
Expand All @@ -21,7 +22,7 @@ const GetDesk = gql`
}
`

export async function listFeedArticles() {
export async function listFeedArticles(): Promise<_NormalizeArticle[]> {
const allArticles = (await $fetch('/_storipress/posts/__all.json')) ?? []
return allArticles
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`addFeedPageLinks > can add feed page links 1`] = `
"<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<id>https://example.com/</id>
<title>site name</title>
<updated>2024-04-24T00:00:00.000Z</updated>
<generator>https://github.com/jpmonette/feed</generator>
<link rel="alternate" href="https://example.com/"></link>
<link rel="self" href="https://example.com/atom.xml"></link>
<link rel="previous" href="https://example.com/atom.xml?page=1"></link>
<link rel="next" href="https://example.com/atom.xml?page=3"></link>
<subtitle>site description</subtitle>
<rights>© site name 2024 All Rights Reserved</rights>
<entry>
<title type="html">
<![CDATA[title]]>
</title>
<id>https://example.com/post</id>
<link href="https://example.com/post"></link>
<updated>2024-04-24T00:00:00.000Z</updated>
</entry>
</feed>
"
`;

exports[`createFeed > can create feed with default config 1`] = `
"<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<id>https://example.com/</id>
<title>site name</title>
<updated>2024-04-24T00:00:00.000Z</updated>
<generator>https://github.com/jpmonette/feed</generator>
<link rel="alternate" href="https://example.com/"/>
<link rel="self" href="https://example.com/atom.xml"/>
<subtitle>site description</subtitle>
<rights>© site name 2024 All Rights Reserved</rights>
<entry>
<title type="html"><![CDATA[title]]></title>
<id>https://example.com/post</id>
<link href="https://example.com/post"/>
<updated>2024-04-24T00:00:00.000Z</updated>
</entry>
</feed>"
`;

exports[`generateAtomFeed > can generate atom feed 1`] = `
"<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<id>https://example.com/</id>
<title>site name</title>
<updated>2024-04-24T00:00:00.000Z</updated>
<generator>https://github.com/jpmonette/feed</generator>
<link rel="alternate" href="https://example.com/"/>
<link rel="self" href="https://example.com/atom.xml"/>
<subtitle>site description</subtitle>
<rights>© site name 2024 All Rights Reserved</rights>
<entry>
<title type="html"><![CDATA[title]]></title>
<id>https://example.com/posts/slug</id>
<link href="https://example.com/posts/slug"/>
<updated>2024-04-24T00:00:00.000Z</updated>
<summary type="html"><![CDATA[plaintext]]></summary>
<content type="html"><![CDATA[html]]></content>
<author>
<name>author</name>
</author>
<published>2024-04-24T00:00:00.000Z</published>
</entry>
</feed>"
`;
82 changes: 82 additions & 0 deletions packages/karbon/src/runtime/lib/__tests__/feed.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { addFeedPageLinks, createFeed, generateAtomFeed } from '../feed'

beforeEach(() => {
vi.useFakeTimers()
vi.setSystemTime(new Date('2024-04-24T00:00:00.000Z'))
})

afterEach(() => {
vi.useRealTimers()
})

describe('createFeed', () => {
it('can create feed with default config', () => {
const feed = createFeed({
siteName: 'site name',
siteDescription: 'site description',
siteUrl: 'https://example.com',
feedUrl: 'atom.xml',
})

feed.addItem({
title: 'title',
date: new Date('2024-04-24T00:00:00.000Z'),
link: 'https://example.com/post',
})

expect(feed.atom1()).toMatchSnapshot()
})
})

describe('generateAtomFeed', () => {
it('can generate atom feed', () => {
const atom = generateAtomFeed({
articles: [
{
id: '1',
slug: 'slug',
authors: [{ name: 'author' }] as any,
title: 'title',
html: 'html',
plaintext: 'plaintext',
updated_at: '2024-04-24T00:00:00.000Z',
published_at: '2024-04-24T00:00:00.000Z',
},
],
getArticleURL: () => '/posts/slug',
siteName: 'site name',
siteDescription: 'site description',
siteUrl: 'https://example.com',
feedUrl: 'atom.xml',
})

expect(atom).toMatchSnapshot()
})
})

const ATOM_FIXTURE = `
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<id>https://example.com/</id>
<title>site name</title>
<updated>2024-04-24T00:00:00.000Z</updated>
<generator>https://github.com/jpmonette/feed</generator>
<link rel="alternate" href="https://example.com/"/>
<link rel="self" href="https://example.com/atom.xml"/>
<subtitle>site description</subtitle>
<rights>© site name 2024 All Rights Reserved</rights>
<entry>
<title type="html"><![CDATA[title]]></title>
<id>https://example.com/post</id>
<link href="https://example.com/post"/>
<updated>2024-04-24T00:00:00.000Z</updated>
</entry>
</feed>
`

describe('addFeedPageLinks', () => {
it('can add feed page links', () => {
expect(addFeedPageLinks(ATOM_FIXTURE, 'https://example.com', 2, 3)).toMatchSnapshot()
})
})
107 changes: 107 additions & 0 deletions packages/karbon/src/runtime/lib/feed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { Feed } from 'feed'
import { encodePath, joinURL, withQuery, withTrailingSlash, withoutTrailingSlash } from 'ufo'
import { XMLBuilder, XMLParser } from 'fast-xml-parser'
import type { _NormalizeArticle } from '../api/normalize-article'

export function createFeed({
siteUrl,
siteName,
siteDescription,
feedUrl,
}: {
siteUrl: string
siteName: string
siteDescription: string
feedUrl: string
}) {
return new Feed({
id: withTrailingSlash(siteUrl),
link: withTrailingSlash(siteUrl),
title: siteName,
description: siteDescription,
updated: new Date(),
feedLinks: {
atom: joinURL(siteUrl, feedUrl),
},
copyright: ${siteName} ${new Date().getFullYear()} All Rights Reserved`,
})
}

export function addFeedPageLinks(atomXml: string, siteUrl: string, currentPage: number, maxPage: number): string {
const option = {
ignoreAttributes: false,
attributeNamePrefix: '@_',
cdataPropName: '__cdata',
format: true,
}
const parser = new XMLParser(option)
const builder = new XMLBuilder(option)

const atomJson = parser.parse(atomXml)

const rssUrl = `${withoutTrailingSlash(siteUrl)}/atom.xml`
const previousLink =
currentPage > 1 ? [{ '@_rel': 'previous', '@_href': withQuery(rssUrl, { page: currentPage - 1 }) }] : []
const nextLink =
currentPage < maxPage ? [{ '@_rel': 'next', '@_href': withQuery(rssUrl, { page: currentPage + 1 }) }] : []

const buildAtomXml = builder.build({
...atomJson,
feed: {
...atomJson.feed,
link: [...atomJson.feed.link, ...previousLink, ...nextLink],
},
})
return buildAtomXml
}

export type FeedArticle = Pick<
_NormalizeArticle,
'id' | 'slug' | 'plaintext' | 'authors' | 'updated_at' | 'published_at' | 'title' | 'html'
>

export interface GenerateAtomFeedInput {
articles: FeedArticle[]
siteUrl: string
siteName: string
siteDescription: string
feedUrl: string
getArticleURL: (article: FeedArticle) => string
}

export function generateAtomFeed({
articles,
siteUrl,
siteName,
siteDescription,
feedUrl,
getArticleURL,
}: GenerateAtomFeedInput): string {
const feed = createFeed({
siteUrl,
siteName,
siteDescription,
feedUrl,
})

articles
.filter((article) => article.published_at)
.forEach((article) => {
const id = encodePath(getArticleURL(article))
feed.addItem({
title: article.title,
id: joinURL(siteUrl, id),
link: joinURL(siteUrl, id),
description: article.plaintext.slice(0, 120),
date: new Date(article.updated_at),
published: new Date(article.published_at),
author:
article.authors?.map((author) => ({
name: author.name,
})) || [],
content: article.html,
})
})

return feed.atom1()
}
43 changes: 11 additions & 32 deletions packages/karbon/src/runtime/routes/atom-desk.xml.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { defineEventHandler, sendNoContent, setHeader } from 'h3'
import { Feed } from 'feed'
import { encodePath, joinURL, withTrailingSlash } from 'ufo'
import path from 'pathe'
import { getDeskWithSlug, listArticles } from '@storipress/karbon/internal'
import { generateAtomFeed, getDeskWithSlug, listArticles } from '@storipress/karbon/internal'
import { useRuntimeConfig } from '#imports'
import urls from '#sp-internal/storipress-urls.mjs'

Expand All @@ -21,39 +19,20 @@ export default defineEventHandler(async (e) => {
return sendNoContent(e, 404)
}

const deskIds: string[] = desk.desks?.map(({ id }: { id: string }) => id) ?? []
const subDesks: string[] = desk.desks?.map(({ id }: { id: string }) => id) ?? []

const runtimeConfig = useRuntimeConfig()
const articles = await listArticles({ desk_ids: deskIds })
const articles = await listArticles({ desk_ids: [desk.id, ...subDesks] })

const siteUrl = runtimeConfig.public.siteUrl as string
const feed = new Feed({
id: withTrailingSlash(runtimeConfig.public.siteUrl as string),
link: withTrailingSlash(runtimeConfig.public.siteUrl as string),
title: runtimeConfig.public.siteName as string,
description: runtimeConfig.public.siteDescription as string,
updated: new Date(),
feedLinks: {
atom: joinURL(siteUrl, `/atom/${fileName}`),
},
copyright: ${runtimeConfig.public.siteName} ${new Date().getFullYear()} All Rights Reserved`,
})

articles.forEach((article) => {
const id = encodePath(urls.article.toURL(article, urls.article._context))
feed.addItem({
title: article.title,
id: joinURL(siteUrl, id),
link: joinURL(siteUrl, id),
description: article.plaintext.slice(0, 120),
date: new Date(article.published_at),
author:
article.authors?.map((author) => ({
name: author.name,
})) || [],
content: article.html,
})
const atomXml = generateAtomFeed({
articles,
siteUrl,
siteName: runtimeConfig.public.siteName as string,
siteDescription: runtimeConfig.public.siteDescription as string,
feedUrl: `/atom/${fileName}`,
getArticleURL: (article) => urls.article.toURL(article, urls.article._context),
})

return feed.atom1()
return atomXml
})
Loading

0 comments on commit 8206a23

Please sign in to comment.