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

Commit

Permalink
refactor: use shared atom generating function
Browse files Browse the repository at this point in the history
  • Loading branch information
DanSnow committed Apr 22, 2024
1 parent 909c9bb commit 782e766
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 85 deletions.
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
Expand Up @@ -44,3 +44,29 @@ exports[`createFeed > can create feed with default config 1`] = `
</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>"
`;
28 changes: 27 additions & 1 deletion packages/karbon/src/runtime/lib/__tests__/feed.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { addFeedPageLinks, createFeed } from '../feed'
import { addFeedPageLinks, createFeed, generateAtomFeed } from '../feed'

beforeEach(() => {
vi.useFakeTimers()
Expand Down Expand Up @@ -29,6 +29,32 @@ describe('createFeed', () => {
})
})

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">
Expand Down
56 changes: 54 additions & 2 deletions packages/karbon/src/runtime/lib/feed.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Feed } from 'feed'
import { joinURL, withQuery, withTrailingSlash, withoutTrailingSlash } from 'ufo'
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,
Expand All @@ -26,7 +27,7 @@ export function createFeed({
})
}

export function addFeedPageLinks(atomXml: string, siteUrl: string, currentPage: number, maxPage: number) {
export function addFeedPageLinks(atomXml: string, siteUrl: string, currentPage: number, maxPage: number): string {
const option = {
ignoreAttributes: false,
attributeNamePrefix: '@_',
Expand All @@ -53,3 +54,54 @@ export function addFeedPageLinks(atomXml: string, siteUrl: string, currentPage:
})
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
})
66 changes: 17 additions & 49 deletions packages/karbon/src/runtime/routes/atom.xml.ts
Original file line number Diff line number Diff line change
@@ -1,74 +1,42 @@
import type { H3Event } from 'h3'
import { defineEventHandler, getQuery, setHeader } from 'h3'
import type { RuntimeConfig } from '@nuxt/schema'
import { encodePath, joinURL } from 'ufo'
import { addFeedPageLinks, createFeed, listFeedArticles } from '@storipress/karbon/internal'
import type { Author } from '../composables/page-meta'
import type { FeedArticle } from '@storipress/karbon/internal'
import { addFeedPageLinks, generateAtomFeed, listFeedArticles } from '@storipress/karbon/internal'
import type { _NormalizeArticle } from '../api/normalize-article'
import { useRuntimeConfig } from '#imports'
import urls from '#sp-internal/storipress-urls.mjs'

interface TArticle {
title: string
id: string
link: string
description: string
content: string
date: Date
authors: Author[]
plaintext: string
html: string
published_at: string
updated_at: string
}

export default defineEventHandler(async (event) => {
setHeader(event, 'Content-Type', 'text/xml; charset=UTF-8')
if (!process.dev) setHeader(event, 'Cache-Control', 'max-age=600, must-revalidate')

const runtimeConfig = useRuntimeConfig()
return await generateAtomFeed(runtimeConfig, event)
})

const ARTICLES_PER_PAGE = 100
const siteUrl = runtimeConfig.public.siteUrl as string

async function generateAtomFeed(runtimeConfig: RuntimeConfig, event: H3Event) {
const articles = await listFeedArticles()
const { currentPageArticles, currentPage, maxPage } = paginateArticle(event, await listFeedArticles())

const siteUrl = runtimeConfig.public.siteUrl as string
const feed = createFeed({
const atomXml = generateAtomFeed({
articles: currentPageArticles,
siteUrl,
siteName: runtimeConfig.public.siteName as string,
siteDescription: runtimeConfig.public.siteDescription as string,
feedUrl: 'atom.xml',
getArticleURL: (article) => urls.article.toURL(article, urls.article._context),
})

const buildAtomXml = addFeedPageLinks(atomXml, siteUrl, currentPage, maxPage)

return buildAtomXml
})

const ARTICLES_PER_PAGE = 100

function paginateArticle(event: H3Event, articles: FeedArticle[]) {
const queryString = getQuery(event)
const page = Number(queryString.page) || 1
const maxPage = Math.ceil(articles.length / ARTICLES_PER_PAGE)
const currentPage = page > maxPage ? maxPage : page
const currentPageArticles = articles.slice((currentPage - 1) * ARTICLES_PER_PAGE, currentPage * ARTICLES_PER_PAGE)

currentPageArticles
.filter((article: TArticle) => article.published_at)
.forEach((article: TArticle) => {
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.updated_at),
published: new Date(article.published_at),
author:
article.authors?.map((author) => ({
name: author.name,
})) || [],
content: article.html,
})
})

const atomXml = feed.atom1()

const buildAtomXml = addFeedPageLinks(atomXml, siteUrl, currentPage, maxPage)
return buildAtomXml
return { currentPageArticles, currentPage, maxPage }
}

0 comments on commit 782e766

Please sign in to comment.